-
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Context.ts
291 lines (265 loc) · 9.88 KB
/
Context.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
/**
* We're doing the long way around here with assignTag, TagBase & TagBaseTagged,
* because there's a typescript compiler issue where it will complain about Equal.symbol, and Hash.symbol not being accessible.
* https://github.com/microsoft/TypeScript/issues/52644
*/
import type { Scope } from "@effect-app/core"
import { Effect, Layer } from "@effect-app/core"
import * as Context from "effect/Context"
export * from "effect/Context"
export const ServiceTag = Symbol()
export type ServiceTag = typeof ServiceTag
export abstract class PhantomTypeParameter<Identifier extends keyof any, InstantiatedType> {
protected abstract readonly [ServiceTag]: {
readonly [NameP in Identifier]: (_: InstantiatedType) => InstantiatedType
}
}
export type ServiceShape<T extends Context.TagClassShape<any, any>> = Omit<
T,
keyof Context.TagClassShape<any, any>
>
/**
* @tsplus type ServiceTagged
*/
export abstract class ServiceTagged<ServiceKey> extends PhantomTypeParameter<string, ServiceKey> {}
/**
* @tsplus static ServiceTagged make
*/
export function makeService<T extends ServiceTagged<any>>(_: Omit<T, ServiceTag>) {
return _ as T
}
let i = 0
const randomId = () => "unknown-service-" + i++
export function assignTag<Id, Service = Id>(key?: string, creationError?: Error) {
return <S extends object>(cls: S): S & Context.Tag<Id, Service> => {
const tag = Context.GenericTag<Id, Service>(key ?? randomId())
let fields = tag
if (Reflect.ownKeys(cls).includes("key")) {
const { key, ...rest } = tag
fields = rest as any
}
const t = Object.assign(cls, Object.getPrototypeOf(tag), fields)
if (!creationError) {
const limit = Error.stackTraceLimit
Error.stackTraceLimit = 2
creationError = new Error()
Error.stackTraceLimit = limit
}
// the stack is used to get the location of the tag definition, if a service is not found in the registry
Object.defineProperty(t, "stack", {
get() {
return creationError!.stack
}
})
return t
}
}
export type ServiceAcessorShape<Self, Type> =
& (Type extends Record<PropertyKey, any> ? {
[
k in keyof Type as Type[k] extends ((...args: [...infer Args]) => infer Ret)
? ((...args: Readonly<Args>) => Ret) extends Type[k] ? k : never
: k
]: Type[k] extends (...args: [...infer Args]) => Effect<infer A, infer E, infer R>
? (...args: Readonly<Args>) => Effect<A, E, Self | R>
: Type[k] extends (...args: [...infer Args]) => infer A ? (...args: Readonly<Args>) => Effect<A, never, Self>
: Type[k] extends Effect<infer A, infer E, infer R> ? Effect<A, E, Self | R>
: Effect<Type[k], never, Self>
}
: {})
& {
use: <X>(
body: (_: Type) => X
) => X extends Effect<infer A, infer E, infer R> ? Effect<A, E, R | Self> : Effect<X, never, Self>
}
export const proxify = <T extends object>(Tag: T) =>
<Self, Shape>():
& T
& ServiceAcessorShape<Self, Shape> =>
{
const cache = new Map()
const done = new Proxy(Tag, {
get(_target: any, prop: any, _receiver) {
if (prop === "use") {
// @ts-expect-error abc
return (body) => Effect.andThen(Tag, body)
}
if (prop in Tag) {
return Tag[prop]
}
if (cache.has(prop)) {
return cache.get(prop)
}
// @ts-expect-error abc
const fn = (...args: Array<any>) => Effect.andThen(Tag, (s: any) => s[prop](...args))
// @ts-expect-error abc
const cn = Effect.andThen(Tag, (s) => s[prop])
Object.assign(fn, cn)
Object.setPrototypeOf(fn, Object.getPrototypeOf(cn))
cache.set(prop, fn)
return fn
}
})
return done
}
// export const TagMake = <ServiceImpl, R, E, const Key extends string>(
// key: Key,
// make: Effect<ServiceImpl, E, R>
// ) =>
// <Id>() => {
// const limit = Error.stackTraceLimit
// Error.stackTraceLimit = 2
// const creationError = new Error()
// Error.stackTraceLimit = limit
// const c: {
// new(): Context.TagClassShape<Key, ServiceImpl>
// toLayer: () => Layer<Id, E, R>
// toLayerScoped: () => Layer<Id, E, Exclude<R, Scope>>
// } = class {
// static toLayer = () => {
// return Layer.effect(this as any, make)
// }
// static toLayerScoped = () => {
// return Layer.scoped(this as any, make)
// }
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// } as any
// return proxify(assignTag<Id, ServiceImpl>(key, creationError)(c))<Id, ServiceImpl>()
// }
// export function Tag<Id, ServiceImpl, Service = Id>(key?: string) {
// const limit = Error.stackTraceLimit
// Error.stackTraceLimit = 2
// const creationError = new Error()
// Error.stackTraceLimit = limit
// const c: (abstract new(impl: ServiceImpl) => Readonly<ServiceImpl>) & {
// toLayer: <E, R>(eff: Effect<ServiceImpl, E, R>) => Layer<Id, E, R>
// toLayerScoped: <E, R>(eff: Effect<ServiceImpl, E, R>) => Layer<Id, E, Exclude<R, Scope>>
// } = class {
// constructor(service: ServiceImpl) {
// Object.assign(this, service)
// }
// static _key?: string
// static toLayer = <E, R>(eff: Effect<ServiceImpl, E, R>) => {
// return Layer.effect(this as any, eff)
// }
// static toLayerScoped = <E, R>(eff: Effect<ServiceImpl, E, R>) => {
// return Layer.scoped(this as any, eff)
// }
// static get key() {
// return this._key ?? (this._key = key ?? creationError.stack?.split("\n")[2] ?? this.name)
// }
// } as any
// return proxify(assignTag<Id, Service>(key, creationError)(c))<Id, ServiceImpl>()
// }
// export const TagMake = <ServiceImpl, R, E>(
// make: Effect<ServiceImpl, E, R>,
// key?: string
// ) =>
// <Id, Service = Id>() => {
// const limit = Error.stackTraceLimit
// Error.stackTraceLimit = 2
// const creationError = new Error()
// Error.stackTraceLimit = limit
// const c: (abstract new(impl: ServiceImpl) => Readonly<ServiceImpl>) & {
// toLayer: { (): Layer<Id, E, R>; <E, R>(eff: Effect<ServiceImpl, E, R>): Layer<Id, E, R> }
// toLayerScoped: {
// (): Layer<Id, E, Exclude<R, Scope>>
// <E, R>(eff: Effect<ServiceImpl, E, R>): Layer<Id, E, Exclude<R, Scope>>
// }
// make: Effect<Id, E, R>
// } = class {
// constructor(service: ServiceImpl) {
// Object.assign(this, service)
// }
// static _key: string
// static make = make
// // works around an issue where defining layer on the class messes up and causes the Tag to infer to `any, any` :/
// static toLayer = (arg?: any) => {
// return Layer.effect(this as any, arg ?? this.make)
// }
// static toLayerScoped = (arg?: any) => {
// return Layer.scoped(this as any, arg ?? this.make)
// }
// static get key() {
// return this._key ?? (this._key = key ?? creationError.stack?.split("\n")[2] ?? this.name)
// }
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// } as any
// return proxify(assignTag<Id, Service>(key, creationError)(c))<Id, ServiceImpl>()
// }
export function TagId<const Key extends string>(key: Key) {
return <Id, ServiceImpl>() => {
const limit = Error.stackTraceLimit
Error.stackTraceLimit = 2
const creationError = new Error()
Error.stackTraceLimit = limit
const c:
& (abstract new(
service: ServiceImpl
) => Readonly<ServiceImpl> & Context.TagClassShape<Key, ServiceImpl>)
& {
toLayer: <E, R>(eff: Effect<Omit<Id, keyof Context.TagClassShape<any, any>>, E, R>) => Layer<Id, E, R>
toLayerScoped: <E, R>(
eff: Effect<Omit<Id, keyof Context.TagClassShape<any, any>>, E, R>
) => Layer<Id, E, Exclude<R, Scope>>
of: (service: Omit<Id, keyof Context.TagClassShape<any, any>>) => Id
} = class {
constructor(service: any) {
// TODO: instead, wrap the service, and direct calls?
Object.assign(this, service)
}
static of = (service: ServiceImpl) => service
static toLayer = <E, R>(eff: Effect<ServiceImpl, E, R>) => {
return Layer.effect(this as any, eff)
}
static toLayerScoped = <E, R>(eff: Effect<ServiceImpl, E, R>) => {
return Layer.scoped(this as any, eff)
}
} as any
return proxify(assignTag<Id, Id>(key, creationError)(c))<Id, ServiceImpl>()
}
}
export const TagMakeId = <ServiceImpl, R, E, const Key extends string>(
key: Key,
make: Effect<ServiceImpl, E, R>
) =>
<Id>() => {
const limit = Error.stackTraceLimit
Error.stackTraceLimit = 2
const creationError = new Error()
Error.stackTraceLimit = limit
const c:
& (abstract new(
service: ServiceImpl
) => Readonly<ServiceImpl> & Context.TagClassShape<Key, ServiceImpl>)
& {
toLayer: {
(): Layer<Id, E, R>
<E, R>(eff: Effect<Omit<Id, keyof Context.TagClassShape<any, any>>, E, R>): Layer<Id, E, R>
}
toLayerScoped: {
(): Layer<Id, E, Exclude<R, Scope>>
<E, R>(eff: Effect<Context.TagClassShape<any, any>, E, R>): Layer<Id, E, Exclude<R, Scope>>
}
of: (service: Context.TagClassShape<any, any>) => Id
make: Effect<Id, E, R>
} = class {
constructor(service: any) {
// TODO: instead, wrap the service, and direct calls?
Object.assign(this, service)
}
static of = (service: ServiceImpl) => service
static make = make
// works around an issue where defining layer on the class messes up and causes the Tag to infer to `any, any` :/
static toLayer = (arg?: any) => {
return Layer.effect(this as any, arg ?? this.make)
}
static toLayerScoped = (arg?: any) => {
return Layer.scoped(this as any, arg ?? this.make)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
return proxify(assignTag<Id, Id>(key, creationError)(c))<Id, ServiceImpl>()
}