veffect
Version:
powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha
263 lines (239 loc) • 7.41 kB
text/typescript
import type * as C from "../Context.js"
import * as Equal from "../Equal.js"
import { dual } from "../Function.js"
import * as Hash from "../Hash.js"
import { format, NodeInspectSymbol, toJSON } from "../Inspectable.js"
import type * as O from "../Option.js"
import { pipeArguments } from "../Pipeable.js"
import { hasProperty } from "../Predicate.js"
import type * as STM from "../STM.js"
import { EffectPrototype, effectVariance } from "./effectable.js"
import * as option from "./option.js"
/** @internal */
export const TagTypeId: C.TagTypeId = Symbol.for("effect/Context/Tag") as C.TagTypeId
/** @internal */
const STMSymbolKey = "effect/STM"
/** @internal */
export const STMTypeId: STM.STMTypeId = Symbol.for(
STMSymbolKey
) as STM.STMTypeId
/** @internal */
export const TagProto: any = {
...EffectPrototype,
_tag: "Tag",
_op: "Tag",
[STMTypeId]: effectVariance,
[TagTypeId]: {
_Service: (_: unknown) => _,
_Identifier: (_: unknown) => _
},
toString() {
return format(this.toJSON())
},
toJSON<I, A>(this: C.Tag<I, A>) {
return {
_id: "Tag",
key: this.key,
stack: this.stack
}
},
[NodeInspectSymbol]() {
return this.toJSON()
},
of<Service>(self: Service): Service {
return self
},
context<Identifier, Service>(
this: C.Tag<Identifier, Service>,
self: Service
): C.Context<Identifier> {
return make(this, self)
}
}
/** @internal */
export const makeGenericTag = <Identifier, Service = Identifier>(key: string): C.Tag<Identifier, Service> => {
const limit = Error.stackTraceLimit
Error.stackTraceLimit = 2
const creationError = new Error()
Error.stackTraceLimit = limit
const tag = Object.create(TagProto)
Object.defineProperty(tag, "stack", {
get() {
return creationError.stack
}
})
tag.key = key
return tag
}
/** @internal */
export const Tag = <const Id extends string>(id: Id) => <Self, Shape>(): C.TagClass<Self, Id, Shape> => {
const limit = Error.stackTraceLimit
Error.stackTraceLimit = 2
const creationError = new Error()
Error.stackTraceLimit = limit
function TagClass() {}
Object.setPrototypeOf(TagClass, TagProto)
TagClass.key = id
Object.defineProperty(TagClass, "stack", {
get() {
return creationError.stack
}
})
return TagClass as any
}
/** @internal */
export const TypeId: C.TypeId = Symbol.for("effect/Context") as C.TypeId
/** @internal */
export const ContextProto: Omit<C.Context<unknown>, "unsafeMap"> = {
[TypeId]: {
_Services: (_: unknown) => _
},
[Equal.symbol]<A>(this: C.Context<A>, that: unknown): boolean {
if (isContext(that)) {
if (this.unsafeMap.size === that.unsafeMap.size) {
for (const k of this.unsafeMap.keys()) {
if (!that.unsafeMap.has(k) || !Equal.equals(this.unsafeMap.get(k), that.unsafeMap.get(k))) {
return false
}
}
return true
}
}
return false
},
[Hash.symbol]<A>(this: C.Context<A>): number {
return Hash.cached(this, Hash.number(this.unsafeMap.size))
},
pipe<A>(this: C.Context<A>) {
return pipeArguments(this, arguments)
},
toString() {
return format(this.toJSON())
},
toJSON<A>(this: C.Context<A>) {
return {
_id: "Context",
services: Array.from(this.unsafeMap).map(toJSON)
}
},
[NodeInspectSymbol]() {
return (this as any).toJSON()
}
}
/** @internal */
export const makeContext = <Services>(unsafeMap: Map<string, any>): C.Context<Services> => {
const context = Object.create(ContextProto)
context.unsafeMap = unsafeMap
return context
}
const serviceNotFoundError = (tag: C.Tag<any, any>) => {
const error = new Error(`Service not found${tag.key ? `: ${String(tag.key)}` : ""}`)
if (tag.stack) {
const lines = tag.stack.split("\n")
if (lines.length > 2) {
const afterAt = lines[2].match(/at (.*)/)
if (afterAt) {
error.message = error.message + ` (defined at ${afterAt[1]})`
}
}
}
if (error.stack) {
const lines = error.stack.split("\n")
lines.splice(1, 3)
error.stack = lines.join("\n")
}
return error
}
/** @internal */
export const isContext = (u: unknown): u is C.Context<never> => hasProperty(u, TypeId)
/** @internal */
export const isTag = (u: unknown): u is C.Tag<any, any> => hasProperty(u, TagTypeId)
const _empty = makeContext(new Map())
/** @internal */
export const empty = (): C.Context<never> => _empty
/** @internal */
export const make = <T extends C.Tag<any, any>>(
tag: T,
service: C.Tag.Service<T>
): C.Context<C.Tag.Identifier<T>> => makeContext(new Map([[tag.key, service]]))
/** @internal */
export const add = dual<
<T extends C.Tag<any, any>>(
tag: T,
service: C.Tag.Service<T>
) => <Services>(
self: C.Context<Services>
) => C.Context<Services | C.Tag.Identifier<T>>,
<Services, T extends C.Tag<any, any>>(
self: C.Context<Services>,
tag: T,
service: C.Tag.Service<T>
) => C.Context<Services | C.Tag.Identifier<T>>
>(3, (self, tag, service) => {
const map = new Map(self.unsafeMap)
map.set(tag.key, service)
return makeContext(map)
})
/** @internal */
export const unsafeGet = dual<
<S, I>(tag: C.Tag<I, S>) => <Services>(self: C.Context<Services>) => S,
<Services, S, I>(self: C.Context<Services>, tag: C.Tag<I, S>) => S
>(2, (self, tag) => {
if (!self.unsafeMap.has(tag.key)) {
throw serviceNotFoundError(tag as any)
}
return self.unsafeMap.get(tag.key)! as any
})
/** @internal */
export const get: {
<Services, T extends C.ValidTagsById<Services>>(tag: T): (self: C.Context<Services>) => C.Tag.Service<T>
<Services, T extends C.ValidTagsById<Services>>(self: C.Context<Services>, tag: T): C.Tag.Service<T>
} = unsafeGet
/** @internal */
export const getOption = dual<
<S, I>(tag: C.Tag<I, S>) => <Services>(self: C.Context<Services>) => O.Option<S>,
<Services, S, I>(self: C.Context<Services>, tag: C.Tag<I, S>) => O.Option<S>
>(2, (self, tag) => {
if (!self.unsafeMap.has(tag.key)) {
return option.none
}
return option.some(self.unsafeMap.get(tag.key)! as any)
})
/** @internal */
export const merge = dual<
<R1>(that: C.Context<R1>) => <Services>(self: C.Context<Services>) => C.Context<Services | R1>,
<Services, R1>(self: C.Context<Services>, that: C.Context<R1>) => C.Context<Services | R1>
>(2, (self, that) => {
const map = new Map(self.unsafeMap)
for (const [tag, s] of that.unsafeMap) {
map.set(tag, s)
}
return makeContext(map)
})
/** @internal */
export const pick =
<Services, S extends Array<C.ValidTagsById<Services>>>(...tags: S) =>
(self: C.Context<Services>): C.Context<
{ [k in keyof S]: C.Tag.Identifier<S[k]> }[number]
> => {
const tagSet = new Set<string>(tags.map((_) => _.key))
const newEnv = new Map()
for (const [tag, s] of self.unsafeMap.entries()) {
if (tagSet.has(tag)) {
newEnv.set(tag, s)
}
}
return makeContext<{ [k in keyof S]: C.Tag.Identifier<S[k]> }[number]>(newEnv)
}
/** @internal */
export const omit =
<Services, S extends Array<C.ValidTagsById<Services>>>(...tags: S) =>
(self: C.Context<Services>): C.Context<
Exclude<Services, { [k in keyof S]: C.Tag.Identifier<S[k]> }[keyof S]>
> => {
const newEnv = new Map(self.unsafeMap)
for (const tag of tags) {
newEnv.delete(tag.key)
}
return makeContext(newEnv)
}