diff --git a/package.json b/package.json index 17a0452..cbcb17e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@thilawyn/thilalib", - "version": "0.1.18", + "version": "0.1.19", "type": "module", "files": [ "./dist" diff --git a/src/Schema/MobX/ObservableClass.ts b/src/Schema/MobX/ObservableClass.ts index 61243c7..0b7cabe 100644 --- a/src/Schema/MobX/ObservableClass.ts +++ b/src/Schema/MobX/ObservableClass.ts @@ -3,12 +3,12 @@ import { makeObservable, observable, type CreateObservableOptions } from "mobx" import { mapValues } from "remeda" -interface ObservableClassSelf { +export interface ObservableClassSelf { new(...args: any[]): Schema.Struct.Type readonly fields: { readonly [K in keyof Schema.Struct.Fields]: Schema.Struct.Fields[K] } } -interface ObservableClassOptions extends Omit {} +export interface ObservableClassOptions extends Omit {} export const ObservableClass = (options?: ObservableClassOptions) => (self: Self) => diff --git a/src/Schema/Email.ts b/src/Schema/email.ts similarity index 76% rename from src/Schema/Email.ts rename to src/Schema/email.ts index 7762efd..30a227e 100644 --- a/src/Schema/Email.ts +++ b/src/Schema/email.ts @@ -1,11 +1,11 @@ import { Schema } from "@effect/schema" -export const Email = Schema.pattern( +export const email = Schema.pattern( /^(?!\.)(?!.*\.\.)([A-Z0-9_+-.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i, { - identifier: "Email", + identifier: "email", title: "email", message: () => "Not an email address", }, diff --git a/src/Schema/index.ts b/src/Schema/index.ts index dae09dc..23f5bf3 100644 --- a/src/Schema/index.ts +++ b/src/Schema/index.ts @@ -1,6 +1,6 @@ export * from "./Class" export * from "./DateTime" -export * from "./Email" +export * from "./email" export * from "./encodedAsPrismaJsonValue" export * from "./Kind" export * as MobX from "./MobX" diff --git a/src/TRPC/TRPCContext.ts b/src/TRPC/TRPCContext.ts index 94089d8..f6560f5 100644 --- a/src/TRPC/TRPCContext.ts +++ b/src/TRPC/TRPCContext.ts @@ -6,7 +6,9 @@ import type { IncomingMessage } from "node:http" import type { WebSocket } from "ws" -export interface TRPCContext { +export interface TRPCContext extends TRPCContextRuntime, TRPCContextTransaction {} + +export interface TRPCContextRuntime { readonly runtime: Runtime.Runtime readonly run: ( @@ -18,12 +20,14 @@ export interface TRPCContext { effect: Effect.Effect, options?: Runtime.RunForkOptions, ) => RuntimeFiber +} - readonly transaction: TRPCContextTransaction +export interface TRPCContextTransaction { + readonly transaction: TRPCTransaction } -export type TRPCContextTransaction = Data.TaggedEnum<{ +export type TRPCTransaction = Data.TaggedEnum<{ readonly Express: { readonly req: express.Request readonly res: express.Response @@ -35,4 +39,4 @@ export type TRPCContextTransaction = Data.TaggedEnum<{ } }> -export const TRPCContextTransactionEnum = Data.taggedEnum() +export const TRPCTransactionEnum = Data.taggedEnum() diff --git a/src/TRPC/TRPCContextCreator.ts b/src/TRPC/TRPCContextCreator.ts index 9087b1e..938bd68 100644 --- a/src/TRPC/TRPCContextCreator.ts +++ b/src/TRPC/TRPCContextCreator.ts @@ -2,7 +2,7 @@ import type { CreateExpressContextOptions } from "@trpc/server/adapters/express" import type { CreateWSSContextFnOptions } from "@trpc/server/adapters/ws" import { Context, Effect, Layer, Runtime } from "effect" import { createTRCPErrorMapper } from "./createTRCPErrorMapper" -import { TRPCContextTransactionEnum, type TRPCContext, type TRPCContextTransaction } from "./TRPCContext" +import { TRPCTransactionEnum, type TRPCContext, type TRPCTransaction } from "./TRPCContext" export const Identifier = "@thilalib/TRPC/TRPCContextCreator" @@ -10,7 +10,7 @@ export const Identifier = "@thilalib/TRPC/TRPCContextCreator" export interface TRPCContextCreator extends Context.Tag> {} export interface TRPCContextCreatorService { - readonly createContext: (transaction: TRPCContextTransaction) => TRPCContext + readonly createContext: (transaction: TRPCTransaction) => TRPCContext readonly createExpressContext: (context: CreateExpressContextOptions) => TRPCContext readonly createWebSocketContext: (context: CreateWSSContextFnOptions) => TRPCContext } @@ -41,16 +41,16 @@ export const make = () => { options, ) - const createContext = (transaction: TRPCContextTransaction) => ({ + const createContext = (transaction: TRPCTransaction) => ({ runtime, run, fork, transaction, }) - const createExpressContext = (context: CreateExpressContextOptions) => createContext(TRPCContextTransactionEnum.Express(context)) - const createWebSocketContext = (context: CreateWSSContextFnOptions) => createContext(TRPCContextTransactionEnum.WebSocket(context)) + const createExpressContext = (context: CreateExpressContextOptions) => createContext(TRPCTransactionEnum.Express(context)) + const createWebSocketContext = (context: CreateWSSContextFnOptions) => createContext(TRPCTransactionEnum.WebSocket(context)) - return { createContext, createExpressContext, createWebSocketContext } + return { createContext, createExpressContext, createWebSocketContext } as const })) return { TRPCContextCreator, TRPCContextCreatorLive } as const diff --git a/src/TRPC/middlewares.ts b/src/TRPC/middlewares.ts deleted file mode 100644 index fcd57e3..0000000 --- a/src/TRPC/middlewares.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Effect, Match } from "effect" -import type { TRPCContextTransaction } from "./TRPCContext" -import { importTRPCServer } from "./importTRPCServer" - - -export const ExpressOnly = importTRPCServer.pipe(Effect.map(({ - experimental_standaloneMiddleware, - TRPCError, -}) => experimental_standaloneMiddleware<{ - ctx: { readonly transaction: TRPCContextTransaction } -}>().create(opts => - Match.value(opts.ctx.transaction).pipe( - Match.tag("Express", transaction => - opts.next({ ctx: { transaction } }) - ), - - Match.orElse(() => { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Only Express transport is supported by this procedure", - }) - }), - ) -))) - -export const WebSocketOnly = importTRPCServer.pipe(Effect.map(({ - experimental_standaloneMiddleware, - TRPCError, -}) => experimental_standaloneMiddleware<{ - ctx: { readonly transaction: TRPCContextTransaction } -}>().create(opts => - Match.value(opts.ctx.transaction).pipe( - Match.tag("WebSocket", transaction => - opts.next({ ctx: { transaction } }) - ), - - Match.orElse(() => { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Only WebSocket transport is supported by this procedure", - }) - }), - ) -))) diff --git a/src/TRPC/middlewares/DecodeInput.ts b/src/TRPC/middlewares/DecodeInput.ts new file mode 100644 index 0000000..449d870 --- /dev/null +++ b/src/TRPC/middlewares/DecodeInput.ts @@ -0,0 +1,33 @@ +import { Schema } from "@effect/schema" +import type { ParseOptions } from "@effect/schema/AST" +import { Effect } from "effect" +import { importTRPCServer } from "../importTRPCServer" +import type { TRPCContextRuntime } from "../TRPCContext" + + +export const DecodeInput = ( + schema: Schema.Schema, + options?: ParseOptions, +) => Effect.gen(function*() { + const { experimental_standaloneMiddleware, TRPCError } = yield* importTRPCServer + + const decode = (value: I) => Schema.decode(schema, options)(value).pipe( + Effect.matchEffect({ + onSuccess: Effect.succeed, + onFailure: e => Effect.fail(new TRPCError({ + code: "BAD_REQUEST", + message: "Could not decode input", + cause: e, + })), + }) + ) + + return experimental_standaloneMiddleware<{ + ctx: TRPCContextRuntime + input: I + }>().create( + async ({ ctx, input, next }) => next({ + ctx: { decodedInput: await ctx.run(decode(input)) } as const + }) + ) +}) diff --git a/src/TRPC/middlewares/ExpressOnly.ts b/src/TRPC/middlewares/ExpressOnly.ts new file mode 100644 index 0000000..c6bed4c --- /dev/null +++ b/src/TRPC/middlewares/ExpressOnly.ts @@ -0,0 +1,22 @@ +import { Effect, identity, Match } from "effect" +import { importTRPCServer } from "../importTRPCServer" +import type { TRPCContextTransaction } from "../TRPCContext" + + +export const ExpressOnly = importTRPCServer.pipe(Effect.map(({ + experimental_standaloneMiddleware, + TRPCError, +}) => experimental_standaloneMiddleware<{ + ctx: TRPCContextTransaction +}>().create(({ ctx, next }) => next({ + ctx: Match.value(ctx.transaction).pipe( + Match.tag("Express", identity), + + Match.orElse(() => { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Only Express transport is supported by this procedure", + }) + }), + ) +})))) diff --git a/src/TRPC/middlewares/WebSocketOnly.ts b/src/TRPC/middlewares/WebSocketOnly.ts new file mode 100644 index 0000000..b5da83d --- /dev/null +++ b/src/TRPC/middlewares/WebSocketOnly.ts @@ -0,0 +1,22 @@ +import { Effect, identity, Match } from "effect" +import { importTRPCServer } from "../importTRPCServer" +import type { TRPCContextTransaction } from "../TRPCContext" + + +export const WebSocketOnly = importTRPCServer.pipe(Effect.map(({ + experimental_standaloneMiddleware, + TRPCError, +}) => experimental_standaloneMiddleware<{ + ctx: TRPCContextTransaction +}>().create(({ ctx, next }) => next({ + ctx: Match.value(ctx.transaction).pipe( + Match.tag("WebSocket", identity), + + Match.orElse(() => { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Only WebSocket transport is supported by this procedure", + }) + }), + ) +})))) diff --git a/src/TRPC/middlewares/index.ts b/src/TRPC/middlewares/index.ts new file mode 100644 index 0000000..694686a --- /dev/null +++ b/src/TRPC/middlewares/index.ts @@ -0,0 +1,3 @@ +export * from "./DecodeInput" +export * from "./ExpressOnly" +export * from "./WebSocketOnly"