/** * Originally ported from https://github.com/zio/zio/blob/master/core/shared/src/main/scala/zio/Has.scala * by Michael Arnaldi and the Matechs Garage Contributors * * Copyright 2017-2020 John A. De Goes and the ZIO Contributors * * Copyright 2020 Michael Arnaldi and the Matechs Garage Contributors. */ import type { Option } from './Option' import { fromNullable, None } from './Option' /** * URI used in Has */ export declare const HasURI: unique symbol /** * Has signal presence of a specific service provided via Tag in the environment */ export interface Has { [HasURI]: { _T: () => T } } /** * URI used in Region */ export const RegionURI: unique symbol = Symbol() /** * Branding sub-environments */ export interface Region { [RegionURI]: { _K: () => K _T: () => T } } /** * Extract the type of a class constructor */ export type ConstructorType> = K extends { prototype: infer T } ? T : never export type Constructor = Function & { prototype: T } /** * Tag Encodes capabilities of reading and writing a service T into a generic environment */ export interface Tag { _tag: 'Tag' _T: T key: PropertyKey def: boolean overridable: () => Tag fixed: () => Tag refine: () => Tag read: (r: Has) => T readOption: (r: unknown) => Option setKey: (s: PropertyKey) => Tag of: (_: T) => Has } /** * Extract the Has type from any augumented variant */ export type HasTag = [T] extends [Tag] ? Has : never function makeTag(def = false, key: PropertyKey = Symbol()): Tag { return { _tag: 'Tag', _T: undefined as any, key, def, of: (t) => ({ [key]: t } as any), overridable: () => makeTag(true, key), fixed: () => makeTag(false, key), refine: () => makeTag(def, key), read: (r: Has) => r[key], readOption: (r) => (typeof r === 'object' && r !== null ? fromNullable(r[key]) : None()), setKey: (s: PropertyKey) => makeTag(def, s) } } /** * Create a service entry Tag from a type and a URI */ export function tag>(_: T): Tag> export function tag(): Tag export function tag(_?: any): Tag { return makeTag() } /** * Get the service type of a Has */ export type ServiceType = [T] extends [Has] ? A : never /** * Replaces the service with the required Service Entry, in the specified environment */ export function replaceServiceIn(_: Tag, f: (t: T) => T): (r: R & Has) => R & Has { return (r) => ({ ...r, [_.key]: f(r[_.key]) }) } /** * Replaces the service with the required Service Entry, in the specified environment */ export function replaceServiceIn_(r: R & Has, _: Tag, f: (t: T) => T): R & Has { return { ...r, [_.key]: f(r[_.key]) } as any } /** * Flags the current Has to be overridable, when this is used subsequently provided * environments will override pre-existing. Useful to provide defaults. */ export function overridable(h: Tag): Tag { return { ...h, def: true } } export function mergeEnvironments(_: Tag, r: R1, t: T): R1 & Has { return _.def && r[_.key] ? r : ({ ...r, [_.key]: t } as any) } export class DerivationContext { readonly hasMap = new Map, Tag>() derive(has: Tag, f: () => Tag): Tag { const inMap = this.hasMap.get(has) if (inMap) { return inMap } const computed = f() this.hasMap.set(has, computed) return computed } } export const isTag = (u: unknown): u is Tag => typeof u === 'object' && u != null && '_tag' in u && u['_tag'] === 'Tag'