diff --git a/_project/resources/_src/Graph/Mutation.ts b/_project/resources/_src/Graph/Mutation.ts new file mode 100644 index 000000000..8a5c1d9d5 --- /dev/null +++ b/_project/resources/_src/Graph/Mutation.ts @@ -0,0 +1,54 @@ +import { BlogPost } from "@effect-app-boilerplate/models/Blog" +import { either } from "@effect-app/prelude/schema" +import { CreatePost } from "../Blog.js" +import { MutationErrors } from "../errors.js" +import { GraphQueryRequest, GraphQueryResponse } from "./Query.js" +import { makeMutationInput } from "./utils.js" + +const makeMutationInput_ = makeMutationInput(GraphQueryRequest.Api.props) + +// TODO: Add The follow-up Graph Query +// - parse inputs, when string and starts with $, take from mutation output. +@useClassNameForSchema +@allowAnonymous +@allowRoles("user") +export class GraphMutationRequest extends Post("/graph/mutate")()( + { + CreatePost: optProp( + makeMutationInput_(CreatePost.CreatePostRequest) + ) + // UpdatePurchaseOrder: optProp( + // makeMutationInput_(PurchaseOrders.Update.UpdatePurchaseOrderRequest) + // ) + } +) {} + +const PostResult = props({ + ...GraphQueryResponse.Api.props, + result: optProp(BlogPost) +}) + +@useClassNameForSchema +export class GraphMutationResponse extends Model()({ + // TODO: Support guaranteed optional sub-queries, like on Create/Update of PO + // guarantee an optional return of PurchaseOrder + // first must enable PO cache for guarantee. + CreatePost: optProp( + either( + MutationErrors, + props({ + response: prop(CreatePost.CreatePostResponse), + query: optProp(PostResult) + }) + ) + ) + // UpdatePurchaseOrder: optProp( + // either( + // MutationErrors, + // props({ + // response: optProp(Void), + // query: optProp(POResult) + // }) + // ) + // ) +}) {} diff --git a/_project/resources/_src/Graph/Query.ts b/_project/resources/_src/Graph/Query.ts new file mode 100644 index 000000000..6069de284 --- /dev/null +++ b/_project/resources/_src/Graph/Query.ts @@ -0,0 +1,39 @@ +import { either } from "@effect-app/prelude/schema" +import { QueryErrors } from "../errors.js" +import { BlogRsc } from "../index.js" +import { makeInput } from "./utils.js" + +@useClassNameForSchema +@allowAnonymous +@allowRoles("user") +export class GraphQueryRequest extends Post("/graph/query")()({ + // AllMe: optProp(makeInput(Me.All.GetMeRequest, true)), + // AllMeEventlog: optProp(makeInput(Me.Eventlog.AllMeEventlogRequest, true)), + // AllMeChangeRequests: optProp( + // makeInput(Me.ChangeRequests.AllMeChangeRequestsRequest, true) + // ), + // AllMeCommentActivity: optProp( + // makeInput(Me.CommentActivity.AllMeCommentActivityRequest, true) + // ), + // AllMeTasks: optProp(makeInput(Me.Tasks.AllMeTasksRequest, true)), + + FindBlogPost: optProp(makeInput(BlogRsc.FindPost.FindPostRequest)), + GetAllBlogPosts: optProp(makeInput(BlogRsc.GetPosts.GetPostsRequest)) +}) {} + +@useClassNameForSchema +export class GraphQueryResponse extends Model()({ + // AllMe: optProp(either(QueryErrors, Me.All.GetMeResponse)), + // AllMeEventlog: optProp(either(QueryErrors, Me.Eventlog.AllMeEventlogResponse)), + // AllMeChangeRequests: optProp( + // either(QueryErrors, Me.ChangeRequests.AllMeChangeRequestsResponse) + // ), + // AllMeCommentActivity: optProp( + // either(QueryErrors, Me.CommentActivity.AllMeCommentActivityResponse) + // ), + // AllMeTasks: optProp(either(QueryErrors, Me.Tasks.AllMeTasksResponse)), + + FindBlogPost: optProp( + either(QueryErrors, BlogRsc.FindPost.FindPostResponse) + ) +}) {} diff --git a/_project/resources/_src/Graph/utils.ts b/_project/resources/_src/Graph/utils.ts new file mode 100644 index 000000000..91ddf17a2 --- /dev/null +++ b/_project/resources/_src/Graph/utils.ts @@ -0,0 +1,23 @@ +import type { Property, PropertyRecord, SchemaAny, SchemaProperties } from "@effect-app/prelude/schema" + +export function makeInput( + a: Self +): SchemaProperties<{ + input: Property +}> +export function makeInput( + a: Self, + noInput: true +): SchemaProperties<{ + input: Property +}> +export function makeInput(a: Self, noInput?: boolean): any { + return props(noInput ? { input: optProp(a) } : { input: prop(a) }) +} + +export function makeMutationInput(baseSchemaProps: Props) { + return (a: Self) => { + const query = props({ ...baseSchemaProps, result: optProp(bool) }) + return props({ input: prop(a), query: optProp(query) }) + } +} diff --git a/_project/resources/_src/errors.ts b/_project/resources/_src/errors.ts new file mode 100644 index 000000000..7c3a01c87 --- /dev/null +++ b/_project/resources/_src/errors.ts @@ -0,0 +1,70 @@ +@useClassNameForSchema +export class NotFoundError extends Model()({ + _tag: prop(literal("NotFoundError")), + message: prop(string) +}) {} + +@useClassNameForSchema +export class InvalidStateError extends Model()({ + _tag: prop(literal("InvalidStateError")), + message: prop(string) +}) {} + +@useClassNameForSchema +export class ValidationError extends Model()({ + _tag: prop(literal("ValidationError")), + errors: prop(array(unknown)) // meh +}) {} + +@useClassNameForSchema +export class NotLoggedInError extends Model()({ + _tag: prop(literal("NotLoggedInError")) +}) {} + +@useClassNameForSchema +export class UnauthorizedError extends Model()({ + _tag: prop(literal("UnauthorizedError")) +}) {} + +@useClassNameForSchema +export class OptimisticConcurrencyException extends Model()( + { + _tag: prop(literal("OptimisticConcurrencyException")) + } +) {} + +const MutationOnlyErrors = { + InvalidStateError, + OptimisticConcurrencyException +} + +const GeneralErrors = { + NotFoundError, + NotLoggedInError, + UnauthorizedError, + ValidationError +} + +export const SupportedErrors = union({ + ...MutationOnlyErrors, + ...GeneralErrors +}) + ["|>"](named("SupportedErrors")) + ["|>"](withDefaults) +export type SupportedErrors = ParsedShapeOf + +// ideal? +// export const QueryErrors = union({ ...GeneralErrors }) +// ["|>"](named("QueryErrors")) +// ["|>"](withDefaults) +// export type QueryErrors = ParsedShapeOf +// export const MutationErrors = union({ ...GeneralErrors, ...GeneralErrors }) +// ["|>"](named("MutationErrors")) +// ["|>"](withDefaults) + +// export type MutationErrors = ParsedShapeOf + +export const MutationErrors = SupportedErrors +export const QueryErrors = SupportedErrors +export type MutationErrors = ParsedShapeOf +export type QueryErrors = ParsedShapeOf