mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
9.6 KiB
337 lines
9.6 KiB
import type * as C from "../Context.js"
|
|
import * as Equal from "../Equal.js"
|
|
import type { LazyArg } from "../Function.js"
|
|
import { dual } from "../Function.js"
|
|
import { globalValue } from "../GlobalValue.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 type { NoInfer } from "../Types.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 */
|
|
export const ReferenceTypeId: C.ReferenceTypeId = Symbol.for("effect/Context/Reference") as C.ReferenceTypeId
|
|
|
|
/** @internal */
|
|
const STMSymbolKey = "effect/STM"
|
|
|
|
/** @internal */
|
|
export const STMTypeId: STM.STMTypeId = Symbol.for(
|
|
STMSymbolKey
|
|
) as STM.STMTypeId
|
|
|
|
/** @internal */
|
|
export const TagProto: any = {
|
|
...EffectPrototype,
|
|
_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)
|
|
}
|
|
}
|
|
|
|
export const ReferenceProto: any = {
|
|
...TagProto,
|
|
[ReferenceTypeId]: ReferenceTypeId
|
|
}
|
|
|
|
/** @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 Reference = <Self>() =>
|
|
<const Id extends string, Service>(id: Id, options: {
|
|
readonly defaultValue: () => Service
|
|
}): C.ReferenceClass<Self, Id, Service> => {
|
|
const limit = Error.stackTraceLimit
|
|
Error.stackTraceLimit = 2
|
|
const creationError = new Error()
|
|
Error.stackTraceLimit = limit
|
|
|
|
function ReferenceClass() {}
|
|
Object.setPrototypeOf(ReferenceClass, ReferenceProto)
|
|
ReferenceClass.key = id
|
|
ReferenceClass.defaultValue = options.defaultValue
|
|
Object.defineProperty(ReferenceClass, "stack", {
|
|
get() {
|
|
return creationError.stack
|
|
}
|
|
})
|
|
return ReferenceClass 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)
|
|
|
|
/** @internal */
|
|
export const isReference = (u: unknown): u is C.Reference<any, any> => hasProperty(u, ReferenceTypeId)
|
|
|
|
const _empty = makeContext(new Map())
|
|
|
|
/** @internal */
|
|
export const empty = (): C.Context<never> => _empty
|
|
|
|
/** @internal */
|
|
export const make = <I, S>(tag: C.Tag<I, S>, service: NoInfer<S>): C.Context<I> =>
|
|
makeContext(new Map([[tag.key, service]]))
|
|
|
|
/** @internal */
|
|
export const add = dual<
|
|
<I, S>(
|
|
tag: C.Tag<I, S>,
|
|
service: NoInfer<S>
|
|
) => <Services>(
|
|
self: C.Context<Services>
|
|
) => C.Context<Services | I>,
|
|
<Services, I, S>(
|
|
self: C.Context<Services>,
|
|
tag: C.Tag<I, S>,
|
|
service: NoInfer<S>
|
|
) => C.Context<Services | I>
|
|
>(3, (self, tag, service) => {
|
|
const map = new Map(self.unsafeMap)
|
|
map.set(tag.key, service)
|
|
return makeContext(map)
|
|
})
|
|
|
|
const defaultValueCache = globalValue("effect/Context/defaultValueCache", () => new Map<string, any>())
|
|
const getDefaultValue = (tag: C.Reference<any, any>) => {
|
|
if (defaultValueCache.has(tag.key)) {
|
|
return defaultValueCache.get(tag.key)
|
|
}
|
|
const value = tag.defaultValue()
|
|
defaultValueCache.set(tag.key, value)
|
|
return value
|
|
}
|
|
|
|
/** @internal */
|
|
export const unsafeGetReference = <Services, I, S>(self: C.Context<Services>, tag: C.Reference<I, S>): S => {
|
|
return self.unsafeMap.has(tag.key) ? self.unsafeMap.get(tag.key) : getDefaultValue(tag)
|
|
}
|
|
|
|
/** @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)) {
|
|
if (ReferenceTypeId in tag) return getDefaultValue(tag as any)
|
|
throw serviceNotFoundError(tag)
|
|
}
|
|
return self.unsafeMap.get(tag.key)! as any
|
|
})
|
|
|
|
/** @internal */
|
|
export const get: {
|
|
<I, S>(tag: C.Reference<I, S>): <Services>(self: C.Context<Services>) => S
|
|
<Services, I extends Services, S>(tag: C.Tag<I, S>): (self: C.Context<Services>) => S
|
|
<Services, I, S>(self: C.Context<Services>, tag: C.Reference<I, S>): S
|
|
<Services, I extends Services, S>(self: C.Context<Services>, tag: C.Tag<I, S>): S
|
|
} = unsafeGet as any
|
|
|
|
/** @internal */
|
|
export const getOrElse = dual<
|
|
<S, I, B>(tag: C.Tag<I, S>, orElse: LazyArg<B>) => <Services>(self: C.Context<Services>) => S | B,
|
|
<Services, S, I, B>(self: C.Context<Services>, tag: C.Tag<I, S>, orElse: LazyArg<B>) => S | B
|
|
>(3, (self, tag, orElse) => {
|
|
if (!self.unsafeMap.has(tag.key)) {
|
|
return isReference(tag) ? getDefaultValue(tag) : orElse()
|
|
}
|
|
return self.unsafeMap.get(tag.key)! as any
|
|
})
|
|
|
|
/** @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 isReference(tag) ? option.some(getDefaultValue(tag)) : 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 mergeAll = <T extends Array<unknown>>(
|
|
...ctxs: [...{ [K in keyof T]: C.Context<T[K]> }]
|
|
): C.Context<T[number]> => {
|
|
const map = new Map()
|
|
for (const ctx of ctxs) {
|
|
for (const [tag, s] of ctx.unsafeMap) {
|
|
map.set(tag, s)
|
|
}
|
|
}
|
|
return makeContext(map)
|
|
}
|
|
|
|
/** @internal */
|
|
export const pick =
|
|
<Tags extends ReadonlyArray<C.Tag<any, any>>>(...tags: Tags) =>
|
|
<Services>(self: C.Context<Services>): C.Context<
|
|
Services & C.Tag.Identifier<Tags[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(newEnv)
|
|
}
|
|
|
|
/** @internal */
|
|
export const omit =
|
|
<Tags extends ReadonlyArray<C.Tag<any, any>>>(...tags: Tags) =>
|
|
<Services>(self: C.Context<Services>): C.Context<
|
|
Exclude<Services, C.Tag.Identifier<Tags[number]>>
|
|
> => {
|
|
const newEnv = new Map(self.unsafeMap)
|
|
for (const tag of tags) {
|
|
newEnv.delete(tag.key)
|
|
}
|
|
return makeContext(newEnv)
|
|
}
|
|
|