Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ten-laws-join.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@effect-app/infra": minor
---

support middleware `requires` and simplified api
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@
// would prefer on in frontend, so might want to use a specialised frontend config..
"volar.updateImportsOnFileMove.enabled": false,
"typescript.preferences.includePackageJsonAutoImports": "on",
"typescript.preferences.autoImportFileExcludePatterns": [w
"typescript.preferences.autoImportFileExcludePatterns": [
"node_modules/@sentry/node",
"node_modules/vitest/dist",
"node_modules/@azure/cosmos",
Expand Down
6 changes: 3 additions & 3 deletions packages/infra/src/api/layerUtils.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,19 +7,19 @@ export namespace LayerUtils {
NonEmptyReadonlyArray<Layer.Layer.Any> ? {
[k in keyof Layers]: Layer.Layer.Success<Layers[k]>
}[number]
: never
: Layer.Layer.Success<Layers[number]>

export type GetLayersContext<Layers extends ReadonlyArray<Layer.Layer.Any>> = Layers extends
NonEmptyReadonlyArray<Layer.Layer.Any> ? {
[k in keyof Layers]: Layer.Layer.Context<Layers[k]>
}[number]
: never
: Layer.Layer.Context<Layers[number]>

export type GetLayersError<Layers extends ReadonlyArray<Layer.Layer.Any>> = Layers extends
NonEmptyReadonlyArray<Layer.Layer.Any> ? {
[k in keyof Layers]: Layer.Layer.Error<Layers[k]>
}[number]
: never
: Layer.Layer.Error<Layers[number]>
}

export type ContextTagWithDefault<Id, A, LayerE, LayerR> =
Expand Down
10 changes: 1 addition & 9 deletions packages/infra/src/api/routing.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { determineMethod, isCommand } from "@effect-app/infra/api/routing/utils"
import { Rpc, RpcGroup, RpcServer } from "@effect/rpc"
import { type Array, Duration, Effect, Layer, type NonEmptyReadonlyArray, Predicate, Request, S, Schedule, Schema, type Scope } from "effect-app"
import { type Array, Effect, Layer, type NonEmptyReadonlyArray, Predicate, S, Schedule, Schema, type Scope } from "effect-app"
import type { GetEffectContext, GetEffectError, RPCContextMap } from "effect-app/client/req"
import { type HttpHeaders, HttpRouter } from "effect-app/http"
import { typedKeysOf, typedValuesOf } from "effect-app/utils"
Expand DownExpand Up@@ -819,11 +819,3 @@ export type MakeHandlers<Make, Handlers extends Record<string, any>> = Make exte
* @since 3.9.0
*/
export type MakeDepsOut<Make> = Contravariant.Type<MakeDeps<Make>[Layer.LayerTypeId]["_ROut"]>

export const RequestCacheLayers = Layer.mergeAll(
Layer.setRequestCache(
Request.makeCache({ capacity: 500, timeToLive: Duration.hours(8) })
),
Layer.setRequestCaching(true),
Layer.setRequestBatching(true)
)
10 changes: 5 additions & 5 deletions packages/infra/src/api/routing/middleware/ContextProvider.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -87,9 +87,9 @@ export const mergeContextProviders = <
effect: Effect.Effect<
Effect.Effect<
// we need to merge all contexts into one
Context.Context<GetContext<EffectGenUtils.Success<Tag.Service<TDeps[number]>>>>,
Context.Context<GetContext<EffectGenUtils.Success<Tag.Identifier<TDeps[number]>>>>,
never,
EffectGenUtils.Context<Tag.Service<TDeps[number]>>
EffectGenUtils.Context<Tag.Identifier<TDeps[number]>>
>,
LayerUtils.GetLayersError<{ [K in keyof TDeps]: TDeps[K]["Default"] }>,
LayerUtils.GetLayersSuccess<{ [K in keyof TDeps]: TDeps[K]["Default"] }>
Expand DownExpand Up@@ -174,13 +174,13 @@ export const MergedContextProvider = <
ContextProviderId,
Effect.Effect<
// we need to merge all contexts into one
Context.Context<GetContext<EffectGenUtils.Success<Tag.Service<TDeps[number]>>>>,
Context.Context<GetContext<EffectGenUtils.Success<Tag.Identifier<TDeps[number]>>>>,
never,
EffectGenUtils.Context<Tag.Service<TDeps[number]>>
EffectGenUtils.Context<Tag.Identifier<TDeps[number]>>
>,
LayerUtils.GetLayersError<{ [K in keyof TDeps]: TDeps[K]["Default"] }>,
| Exclude<
Tag.Service<TDeps[number]>,
Tag.Identifier<TDeps[number]>,
LayerUtils.GetLayersSuccess<{ [K in keyof TDeps]: TDeps[K]["Default"] }>
>
| LayerUtils.GetLayersContext<{ [K in keyof TDeps]: TDeps[K]["Default"] }>
Expand Down
56 changes: 39 additions & 17 deletions packages/infra/src/api/routing/middleware/DynamicMiddleware.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,10 +94,10 @@ export type RequestContextMapProvider<RequestContextMap extends Record<string, R
export interface MiddlewareMake<
RequestContextMap extends Record<string, RPCContextMap.Any>, // what services will the middleware provide dynamically to the next, or raise errors.
DynamicMiddlewareProviders extends RequestContextMapProvider<RequestContextMap>, // how to resolve the dynamic middleware
GenericMiddlewareProviders extends NonEmptyReadonlyArray<GenericMiddlewareMaker>,
GenericMiddlewareProviders extends ReadonlyArray<GenericMiddlewareMaker>,
MakeMiddlewareE, // what the middleware construction can fail with
MakeMiddlewareR, // what the middleware requires to be constructed
MiddlewareDependencies extends NonEmptyReadonlyArray<Layer.Layer.Any> // layers provided for the middleware to be constructed
MiddlewareDependencies extends ReadonlyArray<Layer.Layer.Any> // layers provided for the middleware to be constructed
> {
/* dynamic middlewares to be applied based on Request Configuration */
dynamicMiddlewares: DynamicMiddlewareProviders
Expand DownExpand Up@@ -170,8 +170,8 @@ export const makeMiddleware =
>() =>
<
RequestContextProviders extends RequestContextMapProvider<RequestContextMap>, // how to resolve the dynamic middleware
GenericMiddlewareProviders extends NonEmptyReadonlyArray<GenericMiddlewareMaker>,
MiddlewareDependencies extends NonEmptyReadonlyArray<Layer.Layer.Any>, // layers provided for the middlware to be constructed
GenericMiddlewareProviders extends ReadonlyArray<GenericMiddlewareMaker>,
MiddlewareDependencies extends ReadonlyArray<Layer.Layer.Any>, // layers provided for the middlware to be constructed
MakeMiddlewareE = never, // what the middleware construction can fail with
MakeMiddlewareR = never // what the middlware requires to be constructed
>(
Expand DownExpand Up@@ -295,7 +295,7 @@ export const makeMiddlewareBasic =
<
RequestContextMap extends Record<string, RPCContextMap.Any>,
RequestContextProviders extends RequestContextMapProvider<RequestContextMap>, // how to resolve the dynamic middleware
GenericMiddlewareProviders extends NonEmptyReadonlyArray<GenericMiddlewareMaker>
GenericMiddlewareProviders extends ReadonlyArray<GenericMiddlewareMaker>
>(
make: MiddlewareMake<
RequestContextMap,
Expand DownExpand Up@@ -446,22 +446,22 @@ function makeRpcEffect<
}

// updated to support Scope.Scope
export interface RpcMiddleware<Provides, E> {
export interface RpcMiddleware<Provides, E, Requires> {
(options: {
readonly clientId: number
readonly rpc: Rpc.AnyWithProps
readonly payload: unknown
readonly headers: HttpHeaders.Headers
}): Effect.Effect<Provides, E, Scope.Scope>
}): Effect.Effect<Provides, E, Scope.Scope | Requires>
}
export interface RpcMiddlewareWrap<Provides, E> {
export interface RpcMiddlewareWrap<Provides, E, Requires> {
(options: {
readonly clientId: number
readonly rpc: Rpc.AnyWithProps
readonly payload: unknown
readonly headers: HttpHeaders.Headers
readonly next: Effect.Effect<SuccessValue, E, Provides | Scope.Scope>
}): Effect.Effect<SuccessValue, E, Scope.Scope>
readonly next: Effect.Effect<SuccessValue, E, Provides | Scope.Scope | Requires>
}): Effect.Effect<SuccessValue, E, Scope.Scope | Requires>
}

type RpcOptionsOriginal = {
Expand All@@ -484,22 +484,23 @@ type RpcOptionsDynamic<Key extends string, A extends RPCContextMap.Any> = RpcOpt

export type Dynamic<Options> = Options extends RpcOptionsDynamic<any, any> ? true : false

export interface RpcMiddlewareDynamic<A, E, Config> {
export interface RpcMiddlewareDynamic<A, E, R, Config> {
(options: {
readonly config: Config // todo
readonly clientId: number
readonly rpc: Rpc.AnyWithProps
readonly payload: unknown
readonly headers: HttpHeaders.Headers
}): Effect.Effect<Option.Option<Context.Context<A>>, E, Scope.Scope>
}): Effect.Effect<Option.Option<Context.Context<A>>, E, Scope.Scope | R>
}

export interface TagClassDynamicAny<RequestContext extends Record<string, RPCContextMap.Any>>
extends Context.Tag<any, any>
{
readonly [RpcMiddleware.TypeId]: RpcMiddleware.TypeId
readonly optional: boolean
readonly provides?: Context.Tag<any, any> | undefined
readonly optional: boolean // TODO: support optional vs required.. with required, we can eliminate the context..
// readonly provides?: Context.Tag<any, any> | undefined
readonly requires?: Context.Tag<any, any> | ContextRepr | undefined
readonly failure: Schema.Schema.All
readonly requiredForClient: boolean
readonly dynamic: RpcDynamic<any, RequestContext[keyof RequestContext]>
Expand All@@ -522,6 +523,18 @@ export declare namespace TagClass {
} ? ContextRepr.Identifier<Options["provides"]>
: never

/**
* @since 1.0.0
* @category models
*/
export type Requires<Options> = Options extends {
readonly requires: Context.Tag<any, any>
} ? Context.Tag.Identifier<Options["requires"]>
: Options extends {
readonly requires: ContextRepr
} ? ContextRepr.Identifier<Options["requires"]>
: never

/**
* @since 1.0.0
* @category models
Expand DownExpand Up@@ -592,6 +605,9 @@ export declare namespace TagClass {
readonly provides: Options extends { readonly provides: Context.Tag<any, any> } ? Options["provides"]
: Options extends { readonly provides: ContextRepr } ? Options["provides"]
: undefined
readonly requires: Options extends { readonly requires: Context.Tag<any, any> } ? Options["requires"]
: Options extends { readonly requires: ContextRepr } ? Options["requires"]
: undefined
readonly dynamic: Options extends RpcOptionsDynamic<any, any> ? Options["dynamic"]
: undefined
readonly requiredForClient: RequiredForClient<Options>
Expand All@@ -610,16 +626,19 @@ export interface TagClass<
Options,
TagClass.Wrap<Options> extends true ? RpcMiddlewareWrap<
TagClass.Provides<Options>,
TagClass.Requires<Options>,
TagClass.Failure<Options>
>
: Options extends RpcOptionsDynamic<any, any> ? RpcMiddlewareDynamic<
TagClass.Service<Options>,
TagClass.FailureService<Options>,
TagClass.Requires<Options>,
{ [K in Options["dynamic"]["key"]]?: Options["dynamic"]["settings"]["contextActivation"] }
>
: RpcMiddleware<
TagClass.Service<Options>,
TagClass.FailureService<Options>
TagClass.FailureService<Options>,
TagClass.Requires<Options>
>
>
{}
Expand All@@ -636,16 +655,19 @@ export const Tag = <Self>() =>
effect: Effect.Effect<
TagClass.Wrap<Options> extends true ? RpcMiddlewareWrap<
TagClass.Provides<Options>,
TagClass.Failure<Options>
TagClass.Failure<Options>,
TagClass.Requires<Options>
>
: Options extends RpcOptionsDynamic<any, any> ? RpcMiddlewareDynamic<
TagClass.Service<Options>,
TagClass.FailureService<Options>,
TagClass.Requires<Options>,
{ [K in Options["dynamic"]["key"]]?: Options["dynamic"]["settings"]["contextActivation"] }
>
: RpcMiddleware<
TagClass.Service<Options>,
TagClass.FailureService<Options>
TagClass.FailureService<Options>,
TagClass.Requires<Options>
>,
E,
R
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,13 +20,15 @@ export type ContextWithLayer<
& (
| ContextTagWithDefault<
Id,
RpcMiddlewareDynamic<Service, Error, Config>,
// todo
RpcMiddlewareDynamic<Service, Error, any, Config>,
LayerE,
LayerR
>
| ContextTagWithDefault<
Id,
RpcMiddlewareDynamic<Service, Error, Config>,
// todo
RpcMiddlewareDynamic<Service, Error, never, Config>,
LayerE,
LayerR
>
Expand DownExpand Up@@ -84,7 +86,7 @@ export const implementMiddleware = <T extends Record<string, RPCContextMap.Any>>
) => Effect.Effect<
Context.Context<GetEffectContext<T, typeof options["config"]>>,
Effect.Error<ReturnType<Tag.Service<TI[keyof TI]>>>,
Effect.Context<ReturnType<Tag.Identifier<TI[keyof TI]>>>
Effect.Context<ReturnType<Tag.Service<TI[keyof TI]>>>
>,
never,
Tag.Identifier<{ [K in keyof TI]: TI[K] }[keyof TI]>
Expand Down
11 changes: 11 additions & 0 deletions packages/infra/src/api/routing/middleware/generic-middleware.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@ export interface TagClassAny extends Context.Tag<any, any> {
readonly [TypeId]: TypeId
readonly optional: boolean
readonly provides?: Context.Tag<any, any> | ContextRepr | undefined
readonly requires?: Context.Tag<any, any> | ContextRepr | undefined
readonly failure: Schema.Schema.All
readonly requiredForClient: boolean
readonly wrap: boolean
Expand All@@ -38,11 +39,21 @@ export type DynamicMiddlewareMaker<RequestContext extends Record<string, RPCCont
& { Default: Layer.Layer.Any } // todo; and Layer..

export namespace GenericMiddlewareMaker {
export type ApplyServices<A extends TagClassAny, R> = Exclude<R, Provided<A>> | Required<A>
export type ApplyManyServices<A extends NonEmptyReadonlyArray<TagClassAny>, R> =
| Exclude<R, { [K in keyof A]: Provided<A[K]> }[number]>
| { [K in keyof A]: Required<A[K]> }[number]
export type Provided<T> = T extends TagClassAny
? T extends { provides: Context.Tag<any, any> } ? Context.Tag.Identifier<T["provides"]>
: T extends { provides: ContextRepr } ? ContextRepr.Identifier<T["provides"]>
: never
: never

export type Required<T> = T extends TagClassAny
? T extends { requires: Context.Tag<any, any> } ? Context.Tag.Identifier<T["requires"]>
: T extends { requires: ContextRepr } ? ContextRepr.Identifier<T["requires"]>
: never
: never
}

export const genericMiddleware = (i: GenericMiddlewareMaker) => i
Expand Down
Loading