@effect-app/infra 4.0.0-beta.8 → 4.0.0-beta.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +527 -0
- package/dist/CUPS.d.ts +3 -3
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +3 -3
- package/dist/Emailer/Sendgrid.js +1 -1
- package/dist/Emailer/service.d.ts +3 -3
- package/dist/Emailer/service.d.ts.map +1 -1
- package/dist/Emailer/service.js +3 -3
- package/dist/MainFiberSet.d.ts +2 -2
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +3 -3
- package/dist/Model/Repository/internal/internal.d.ts +3 -3
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +11 -7
- package/dist/Model/Repository/makeRepo.d.ts +2 -2
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +1 -1
- package/dist/Model/Repository/validation.d.ts +5 -4
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/query/dsl.d.ts +9 -9
- package/dist/Operations.d.ts +2 -2
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +3 -3
- package/dist/OperationsRepo.d.ts +2 -2
- package/dist/OperationsRepo.d.ts.map +1 -1
- package/dist/OperationsRepo.js +3 -3
- package/dist/QueueMaker/SQLQueue.d.ts +3 -5
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +9 -7
- package/dist/QueueMaker/errors.d.ts +1 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +10 -9
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +11 -9
- package/dist/RequestContext.d.ts +19 -14
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/RequestContext.js +5 -5
- package/dist/RequestFiberSet.d.ts +2 -2
- package/dist/RequestFiberSet.d.ts.map +1 -1
- package/dist/RequestFiberSet.js +5 -5
- package/dist/Store/ContextMapContainer.d.ts +14 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +64 -3
- package/dist/Store/Cosmos.js +1 -1
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +3 -4
- package/dist/Store/Memory.d.ts +2 -2
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +4 -4
- package/dist/Store/service.d.ts +6 -3
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +14 -6
- package/dist/adapters/SQL/Model.d.ts +2 -5
- package/dist/adapters/SQL/Model.d.ts.map +1 -1
- package/dist/adapters/SQL/Model.js +21 -13
- package/dist/adapters/ServiceBus.d.ts +6 -6
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +9 -9
- package/dist/adapters/cosmos-client.d.ts +2 -2
- package/dist/adapters/cosmos-client.d.ts.map +1 -1
- package/dist/adapters/cosmos-client.js +3 -3
- package/dist/adapters/logger.d.ts.map +1 -1
- package/dist/adapters/memQueue.d.ts +2 -2
- package/dist/adapters/memQueue.d.ts.map +1 -1
- package/dist/adapters/memQueue.js +3 -3
- package/dist/adapters/mongo-client.d.ts +2 -2
- package/dist/adapters/mongo-client.d.ts.map +1 -1
- package/dist/adapters/mongo-client.js +3 -3
- package/dist/adapters/redis-client.d.ts +3 -3
- package/dist/adapters/redis-client.d.ts.map +1 -1
- package/dist/adapters/redis-client.js +3 -3
- package/dist/api/ContextProvider.d.ts +6 -6
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +6 -6
- package/dist/api/internal/RequestContextMiddleware.d.ts +1 -1
- package/dist/api/internal/auth.d.ts +1 -1
- package/dist/api/internal/events.d.ts +2 -2
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +7 -5
- package/dist/api/layerUtils.d.ts +5 -5
- package/dist/api/layerUtils.d.ts.map +1 -1
- package/dist/api/layerUtils.js +5 -5
- package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
- package/dist/api/routing/schema/jwt.d.ts +1 -1
- package/dist/api/routing/schema/jwt.d.ts.map +1 -1
- package/dist/api/routing/schema/jwt.js +1 -1
- package/dist/api/routing.d.ts +1 -5
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +3 -2
- package/dist/api/setupRequest.js +4 -4
- package/dist/errorReporter.d.ts +1 -1
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +1 -1
- package/dist/fileUtil.js +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/rateLimit.js +1 -1
- package/examples/query.ts +29 -25
- package/package.json +16 -16
- package/src/CUPS.ts +2 -2
- package/src/Emailer/Sendgrid.ts +1 -1
- package/src/Emailer/service.ts +2 -2
- package/src/MainFiberSet.ts +2 -2
- package/src/Model/Repository/internal/internal.ts +11 -8
- package/src/Model/Repository/makeRepo.ts +2 -2
- package/src/Operations.ts +2 -2
- package/src/OperationsRepo.ts +2 -2
- package/src/QueueMaker/SQLQueue.ts +10 -10
- package/src/QueueMaker/memQueue.ts +41 -42
- package/src/QueueMaker/sbqueue.ts +65 -62
- package/src/RequestContext.ts +4 -4
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +98 -2
- package/src/Store/Cosmos.ts +10 -10
- package/src/Store/Disk.ts +2 -3
- package/src/Store/Memory.ts +4 -6
- package/src/Store/service.ts +14 -5
- package/src/adapters/SQL/Model.ts +76 -71
- package/src/adapters/ServiceBus.ts +8 -8
- package/src/adapters/cosmos-client.ts +2 -2
- package/src/adapters/memQueue.ts +2 -2
- package/src/adapters/mongo-client.ts +2 -2
- package/src/adapters/redis-client.ts +2 -2
- package/src/api/ContextProvider.ts +11 -11
- package/src/api/internal/events.ts +7 -6
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/schema/jwt.ts +2 -3
- package/src/api/routing.ts +7 -6
- package/src/api/setupRequest.ts +3 -3
- package/src/errorReporter.ts +1 -1
- package/src/fileUtil.ts +1 -1
- package/src/rateLimit.ts +2 -2
- package/test/contextProvider.test.ts +5 -5
- package/test/controller.test.ts +12 -9
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/fixtures.d.ts +18 -8
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +11 -9
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/fixtures.ts +10 -8
- package/test/query.test.ts +160 -14
- package/test/rawQuery.test.ts +19 -17
- package/test/requires.test.ts +6 -5
- package/test/rpc-multi-middleware.test.ts +73 -4
- package/test/validateSample.test.ts +1 -1
- package/tsconfig.json +0 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { Data, Effect, Layer,
|
|
1
|
+
import { Context, Data, Effect, type Exit, Layer, RequestResolver } from "effect-app"
|
|
2
|
+
import type { NonEmptyArray } from "effect/Array"
|
|
3
|
+
import { dual } from "effect/Function"
|
|
4
|
+
import * as MutableHashMap from "effect/MutableHashMap"
|
|
5
|
+
import type * as Request from "effect/Request"
|
|
2
6
|
import { ContextMap } from "./service.js"
|
|
3
7
|
|
|
4
8
|
// TODO: we have to create a new contextmap on every request.
|
|
@@ -7,7 +11,7 @@ import { ContextMap } from "./service.js"
|
|
|
7
11
|
// we can call another start after startup. but it would be even better if we could Die on accessing rootmap
|
|
8
12
|
// we could also make the ContextMap optional, and when missing, issue a warning instead?
|
|
9
13
|
|
|
10
|
-
export class ContextMapContainer extends
|
|
14
|
+
export class ContextMapContainer extends Context.Reference("ContextMapContainer", {
|
|
11
15
|
defaultValue: (): ContextMap | "root" => "root"
|
|
12
16
|
}) {
|
|
13
17
|
static readonly layer = Layer.effect(this, ContextMap.make.pipe(Effect.map(ContextMap.of)))
|
|
@@ -18,3 +22,95 @@ export class ContextMapNotStartedError extends Data.TaggedError("ContextMapNotSt
|
|
|
18
22
|
export const getContextMap = ContextMapContainer.asEffect().pipe(
|
|
19
23
|
Effect.filterOrFail((_) => _ !== "root", () => new ContextMapNotStartedError())
|
|
20
24
|
)
|
|
25
|
+
|
|
26
|
+
export const withRequestResolverCache: {
|
|
27
|
+
<A extends Request.Request<any, any>>(options: {
|
|
28
|
+
readonly capacity: number
|
|
29
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
30
|
+
}): (self: RequestResolver.RequestResolver<A>) => RequestResolver.RequestResolver<A>
|
|
31
|
+
<A extends Request.Request<any, any>>(
|
|
32
|
+
self: RequestResolver.RequestResolver<A>,
|
|
33
|
+
options: {
|
|
34
|
+
readonly capacity: number
|
|
35
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
36
|
+
}
|
|
37
|
+
): RequestResolver.RequestResolver<A>
|
|
38
|
+
} = dual(2, <A extends Request.Request<any, any>>(
|
|
39
|
+
self: RequestResolver.RequestResolver<A>,
|
|
40
|
+
options: {
|
|
41
|
+
readonly capacity: number
|
|
42
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
43
|
+
}
|
|
44
|
+
): RequestResolver.RequestResolver<A> => {
|
|
45
|
+
const cacheKey = Symbol()
|
|
46
|
+
const strategy = options.strategy ?? "lru"
|
|
47
|
+
type CacheEntry = {
|
|
48
|
+
readonly entry: Request.Entry<A>
|
|
49
|
+
exit: Exit.Exit<Request.Success<A>, Request.Error<A>> | undefined
|
|
50
|
+
}
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
52
|
+
return RequestResolver.makeWith({
|
|
53
|
+
...(self as any),
|
|
54
|
+
runAll(
|
|
55
|
+
entries: NonEmptyArray<Request.Entry<A>>,
|
|
56
|
+
key: unknown
|
|
57
|
+
) {
|
|
58
|
+
return Effect.flatMap(
|
|
59
|
+
getContextMap.pipe(Effect.orDie),
|
|
60
|
+
(contextMap) => {
|
|
61
|
+
const cache = contextMap.getOrCreateStore<MutableHashMap.MutableHashMap<A, CacheEntry>>(
|
|
62
|
+
cacheKey,
|
|
63
|
+
() => MutableHashMap.empty()
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const uncached: Array<Request.Entry<A>> = []
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const ocached = MutableHashMap.get(cache, entry.request)
|
|
69
|
+
if (ocached._tag === "None") {
|
|
70
|
+
const cached: CacheEntry = { entry, exit: undefined }
|
|
71
|
+
MutableHashMap.set(cache, entry.request, cached)
|
|
72
|
+
const prevComplete = entry.completeUnsafe.bind(entry)
|
|
73
|
+
entry.completeUnsafe = (exit) => {
|
|
74
|
+
cached.exit = exit
|
|
75
|
+
prevComplete(exit)
|
|
76
|
+
}
|
|
77
|
+
uncached.push(entry)
|
|
78
|
+
} else {
|
|
79
|
+
const cached = ocached.value
|
|
80
|
+
if (cached.exit) {
|
|
81
|
+
if (strategy === "lru") {
|
|
82
|
+
MutableHashMap.remove(cache, cached.entry.request)
|
|
83
|
+
MutableHashMap.set(cache, cached.entry.request, cached)
|
|
84
|
+
}
|
|
85
|
+
entry.completeUnsafe(cached.exit as any)
|
|
86
|
+
} else {
|
|
87
|
+
cached.entry.uninterruptible = true
|
|
88
|
+
const prevComplete = cached.entry.completeUnsafe.bind(cached.entry)
|
|
89
|
+
cached.entry.completeUnsafe = (exit) => {
|
|
90
|
+
prevComplete(exit)
|
|
91
|
+
entry.completeUnsafe(exit)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (uncached.length === 0) return Effect.void
|
|
98
|
+
|
|
99
|
+
return Effect.onExit(
|
|
100
|
+
(self as any).runAll(uncached, key),
|
|
101
|
+
() => {
|
|
102
|
+
let toRemove = MutableHashMap.size(cache) - options.capacity
|
|
103
|
+
if (toRemove <= 0) return Effect.void
|
|
104
|
+
for (const k of MutableHashMap.keys(cache)) {
|
|
105
|
+
MutableHashMap.remove(cache, k)
|
|
106
|
+
toRemove--
|
|
107
|
+
if (toRemove <= 0) break
|
|
108
|
+
}
|
|
109
|
+
return Effect.void
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
})
|
package/src/Store/Cosmos.ts
CHANGED
|
@@ -328,15 +328,13 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
328
328
|
.query<M>(q, { partitionKey: mainPartitionKey })
|
|
329
329
|
.fetchAll()
|
|
330
330
|
.then(({ resources }) =>
|
|
331
|
-
resources.map((_) =>
|
|
332
|
-
(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}) as any
|
|
339
|
-
)
|
|
331
|
+
resources.map((_) => ({
|
|
332
|
+
...pipe(
|
|
333
|
+
defaultValues,
|
|
334
|
+
Struct.pick(f.select!.filter((_) => typeof _ === "string") as never[])
|
|
335
|
+
),
|
|
336
|
+
...mapReverseId(_ as any)
|
|
337
|
+
}))
|
|
340
338
|
)
|
|
341
339
|
: container
|
|
342
340
|
.items
|
|
@@ -404,7 +402,9 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
404
402
|
Effect
|
|
405
403
|
.flatMap((x) => {
|
|
406
404
|
if (x.statusCode === 412 || x.statusCode === 404 || x.statusCode === 409) {
|
|
407
|
-
return Effect.fail(
|
|
405
|
+
return Effect.fail(
|
|
406
|
+
new OptimisticConcurrencyException({ type: name, id: e[idKey], code: x.statusCode })
|
|
407
|
+
)
|
|
408
408
|
}
|
|
409
409
|
if (x.statusCode > 299 || x.statusCode < 200) {
|
|
410
410
|
return Effect.die(
|
package/src/Store/Disk.ts
CHANGED
|
@@ -66,11 +66,10 @@ function makeDiskStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValu
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// lock file for cross-process coordination during initialization
|
|
69
|
-
const lockFile = file + ".lock"
|
|
70
69
|
|
|
71
70
|
// wrap initialization in file lock to prevent race conditions in multi-worker setups
|
|
72
71
|
const store = yield* fu.withFileLock(
|
|
73
|
-
|
|
72
|
+
file,
|
|
74
73
|
Effect.gen(function*() {
|
|
75
74
|
const shouldSeed = !(fs.existsSync(file))
|
|
76
75
|
|
|
@@ -143,7 +142,7 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
143
142
|
const storesSem = Semaphore.makeUnsafe(1)
|
|
144
143
|
const primary = yield* makeDiskStoreInt(prefix, idKey, "primary", dir, name, seed, config?.defaultValues)
|
|
145
144
|
const stores = new Map<string, Store<IdKey, Encoded>>([["primary", primary]])
|
|
146
|
-
const ctx = yield* Effect.
|
|
145
|
+
const ctx = yield* Effect.context<R>()
|
|
147
146
|
const getStore = !config?.allowNamespace
|
|
148
147
|
? Effect.succeed(primary)
|
|
149
148
|
: storeId.asEffect().pipe(Effect.flatMap((namespace) => {
|
package/src/Store/Memory.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
|
|
3
|
-
import { Array, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore,
|
|
3
|
+
import { Array, Context, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore, Struct } from "effect-app"
|
|
4
4
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
5
5
|
import { get } from "effect-app/utils"
|
|
6
6
|
import { InfraLogger } from "../logger.js"
|
|
@@ -24,7 +24,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
|
|
|
24
24
|
)
|
|
25
25
|
const n = Struct.pick(i, keys)
|
|
26
26
|
subKeys.forEach((subKey) => {
|
|
27
|
-
n[subKey.key] = i[subKey.key]!.map(Struct.pick(subKey.subKeys))
|
|
27
|
+
n[subKey.key] = i[subKey.key]!.map(Struct.pick(subKey.subKeys as never[]))
|
|
28
28
|
})
|
|
29
29
|
return n as M
|
|
30
30
|
}) as any
|
|
@@ -72,9 +72,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const defaultNs: NonEmptyString255 = NonEmptyString255("primary")
|
|
75
|
-
export class storeId
|
|
76
|
-
extends ServiceMap.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs })
|
|
77
|
-
{}
|
|
75
|
+
export class storeId extends Context.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs }) {}
|
|
78
76
|
|
|
79
77
|
function logQuery(f: FilterArgs<any, any>, defaultValues?: any) {
|
|
80
78
|
return InfraLogger
|
|
@@ -267,7 +265,7 @@ export const makeMemoryStore = () => ({
|
|
|
267
265
|
seed,
|
|
268
266
|
config?.defaultValues
|
|
269
267
|
)
|
|
270
|
-
const ctx = yield* Effect.
|
|
268
|
+
const ctx = yield* Effect.context<R>()
|
|
271
269
|
const stores = new Map([["primary", primary]])
|
|
272
270
|
const getStore = !config?.allowNamespace
|
|
273
271
|
? Effect.succeed(primary)
|
package/src/Store/service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type { UniqueKey } from "@azure/cosmos"
|
|
3
|
-
import { Effect, type NonEmptyReadonlyArray, type Option, type Redacted
|
|
3
|
+
import { Context, Effect, type NonEmptyReadonlyArray, type Option, type Redacted } from "effect-app"
|
|
4
4
|
import type { OptimisticConcurrencyException } from "../errors.js"
|
|
5
5
|
import type { FilterResult } from "../Model/filter/filterApi.js"
|
|
6
6
|
import type { FieldValues } from "../Model/filter/types.js"
|
|
@@ -89,7 +89,7 @@ export interface Store<
|
|
|
89
89
|
queryRaw: <Out>(query: RawQuery<Encoded, Out>) => Effect.Effect<readonly Out[]>
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export class StoreMaker extends
|
|
92
|
+
export class StoreMaker extends Context.Opaque<StoreMaker, {
|
|
93
93
|
make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
94
94
|
name: string,
|
|
95
95
|
idKey: IdKey,
|
|
@@ -161,16 +161,25 @@ export const makeContextMap = () => {
|
|
|
161
161
|
// }
|
|
162
162
|
// }
|
|
163
163
|
|
|
164
|
+
const store = new Map<symbol, unknown>()
|
|
165
|
+
|
|
164
166
|
return {
|
|
165
167
|
get: getEtag,
|
|
166
|
-
set: setEtag
|
|
167
|
-
|
|
168
|
+
set: setEtag,
|
|
169
|
+
getOrCreateStore: <T>(key: symbol, make: () => T): T => {
|
|
170
|
+
let value = store.get(key) as T | undefined
|
|
171
|
+
if (value === undefined) {
|
|
172
|
+
value = make()
|
|
173
|
+
store.set(key, value)
|
|
174
|
+
}
|
|
175
|
+
return value
|
|
176
|
+
}
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
const makeMap = Effect.sync(() => makeContextMap())
|
|
172
181
|
|
|
173
|
-
export class ContextMap extends
|
|
182
|
+
export class ContextMap extends Context.Opaque<ContextMap>()("effect-app/ContextMap", { make: makeMap }) {
|
|
174
183
|
}
|
|
175
184
|
|
|
176
185
|
export type PersistenceModelType<Encoded extends object> = Encoded & {
|
|
@@ -7,10 +7,6 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* @since 1.0.0
|
|
9
9
|
*/
|
|
10
|
-
import * as VariantSchema from "effect/unstable/schema/VariantSchema"
|
|
11
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient"
|
|
12
|
-
import * as SqlResolver from "effect/unstable/sql/SqlResolver"
|
|
13
|
-
import * as SqlSchema from "effect/unstable/sql/SqlSchema"
|
|
14
10
|
import crypto from "crypto" // TODO
|
|
15
11
|
import type { Brand } from "effect/Brand"
|
|
16
12
|
import * as DateTime from "effect/DateTime"
|
|
@@ -24,6 +20,10 @@ import * as Schema from "effect/Schema"
|
|
|
24
20
|
import * as Getter from "effect/SchemaGetter"
|
|
25
21
|
import * as Transformation from "effect/SchemaTransformation"
|
|
26
22
|
import type { Scope } from "effect/Scope"
|
|
23
|
+
import * as VariantSchema from "effect/unstable/schema/VariantSchema"
|
|
24
|
+
import { SqlClient } from "effect/unstable/sql/SqlClient"
|
|
25
|
+
import * as SqlResolver from "effect/unstable/sql/SqlResolver"
|
|
26
|
+
import * as SqlSchema from "effect/unstable/sql/SqlSchema"
|
|
27
27
|
|
|
28
28
|
const {
|
|
29
29
|
Class,
|
|
@@ -190,14 +190,13 @@ export const Generated = <S extends Schema.Top>(
|
|
|
190
190
|
* @since 1.0.0
|
|
191
191
|
* @category generated
|
|
192
192
|
*/
|
|
193
|
-
export interface GeneratedByApp<S extends Schema.Top>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}>
|
|
193
|
+
export interface GeneratedByApp<S extends Schema.Top> extends
|
|
194
|
+
VariantSchema.Field<{
|
|
195
|
+
readonly select: S
|
|
196
|
+
readonly insert: S
|
|
197
|
+
readonly update: S
|
|
198
|
+
readonly json: S
|
|
199
|
+
}>
|
|
201
200
|
{}
|
|
202
201
|
|
|
203
202
|
/**
|
|
@@ -303,8 +302,7 @@ export const FieldOption: <Field extends VariantSchema.Field<any> | Schema.Top>(
|
|
|
303
302
|
) => Field extends Schema.Top ? FieldOption<Field>
|
|
304
303
|
: Field extends VariantSchema.Field<infer S> ? VariantSchema.Field<
|
|
305
304
|
{
|
|
306
|
-
readonly [K in keyof S]: S[K] extends Schema.Top
|
|
307
|
-
? K extends VariantsDatabase ? Schema.OptionFromNullOr<S[K]>
|
|
305
|
+
readonly [K in keyof S]: S[K] extends Schema.Top ? K extends VariantsDatabase ? Schema.OptionFromNullOr<S[K]>
|
|
308
306
|
: optionalOption<S[K]>
|
|
309
307
|
: never
|
|
310
308
|
}
|
|
@@ -545,16 +543,15 @@ export const DateTimeUpdateFromNumber: DateTimeUpdateFromNumber = Field({
|
|
|
545
543
|
* @since 1.0.0
|
|
546
544
|
* @category json
|
|
547
545
|
*/
|
|
548
|
-
export interface JsonFromString<S extends Schema.Top>
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}>
|
|
546
|
+
export interface JsonFromString<S extends Schema.Top> extends
|
|
547
|
+
VariantSchema.Field<{
|
|
548
|
+
readonly select: Schema.fromJsonString<S>
|
|
549
|
+
readonly insert: Schema.fromJsonString<S>
|
|
550
|
+
readonly update: Schema.fromJsonString<S>
|
|
551
|
+
readonly json: S
|
|
552
|
+
readonly jsonCreate: S
|
|
553
|
+
readonly jsonUpdate: S
|
|
554
|
+
}>
|
|
558
555
|
{}
|
|
559
556
|
|
|
560
557
|
/**
|
|
@@ -825,26 +822,28 @@ export const makeDataLoaders = <
|
|
|
825
822
|
const idColumn = options.idColumn as string
|
|
826
823
|
const setMaxBatchSize = options.maxBatchSize ? RequestResolver.batchN(options.maxBatchSize) : identity
|
|
827
824
|
|
|
828
|
-
const insertResolver = SqlResolver
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
825
|
+
const insertResolver = SqlResolver
|
|
826
|
+
.ordered({
|
|
827
|
+
Request: Model.insert,
|
|
828
|
+
Result: Model,
|
|
829
|
+
execute: (request: any) =>
|
|
830
|
+
sql.onDialectOrElse({
|
|
831
|
+
mysql: () =>
|
|
832
|
+
Effect.forEach(request, (request: any) =>
|
|
833
|
+
sql`insert into ${sql(options.tableName)} ${sql.insert(request)};
|
|
836
834
|
select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID();`
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
835
|
+
.unprepared
|
|
836
|
+
.pipe(
|
|
837
|
+
Effect.map(([, results]) => results![0] as any)
|
|
838
|
+
), { concurrency: 10 }),
|
|
839
|
+
orElse: () => sql`insert into ${sql(options.tableName)} ${sql.insert(request).returning("*")}`
|
|
840
|
+
})
|
|
841
|
+
})
|
|
842
|
+
.pipe(
|
|
843
|
+
RequestResolver.setDelay(options.window),
|
|
844
|
+
setMaxBatchSize,
|
|
845
|
+
RequestResolver.withSpan(`${options.spanPrefix}.insertResolver`)
|
|
846
|
+
)
|
|
848
847
|
const insertExecute = SqlResolver.request(insertResolver)
|
|
849
848
|
const insert = (
|
|
850
849
|
insert: S["insert"]["Type"]
|
|
@@ -860,14 +859,16 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID(
|
|
|
860
859
|
})
|
|
861
860
|
) as any
|
|
862
861
|
|
|
863
|
-
const insertVoidResolver = SqlResolver
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
862
|
+
const insertVoidResolver = SqlResolver
|
|
863
|
+
.void({
|
|
864
|
+
Request: Model.insert,
|
|
865
|
+
execute: (request: any) => sql`insert into ${sql(options.tableName)} ${sql.insert(request)}`
|
|
866
|
+
})
|
|
867
|
+
.pipe(
|
|
868
|
+
RequestResolver.setDelay(options.window),
|
|
869
|
+
setMaxBatchSize,
|
|
870
|
+
RequestResolver.withSpan(`${options.spanPrefix}.insertVoidResolver`)
|
|
871
|
+
)
|
|
871
872
|
const insertVoidExecute = SqlResolver.request(insertVoidResolver)
|
|
872
873
|
const insertVoid = (
|
|
873
874
|
insert: S["insert"]["Type"]
|
|
@@ -878,18 +879,20 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID(
|
|
|
878
879
|
})
|
|
879
880
|
) as any
|
|
880
881
|
|
|
881
|
-
const findByIdResolver = SqlResolver
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
882
|
+
const findByIdResolver = SqlResolver
|
|
883
|
+
.findById({
|
|
884
|
+
Id: idSchema,
|
|
885
|
+
Result: Model,
|
|
886
|
+
ResultId(request: any) {
|
|
887
|
+
return request[idColumn]
|
|
888
|
+
},
|
|
889
|
+
execute: (ids: any) => sql`select * from ${sql(options.tableName)} where ${sql.in(idColumn, ids)}`
|
|
890
|
+
})
|
|
891
|
+
.pipe(
|
|
892
|
+
RequestResolver.setDelay(options.window),
|
|
893
|
+
setMaxBatchSize,
|
|
894
|
+
RequestResolver.withSpan(`${options.spanPrefix}.findByIdResolver`)
|
|
895
|
+
)
|
|
893
896
|
const findByIdExecute = SqlResolver.request(findByIdResolver)
|
|
894
897
|
const findById = (
|
|
895
898
|
id: S["fields"][Id]["Type"]
|
|
@@ -904,14 +907,16 @@ select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID(
|
|
|
904
907
|
})
|
|
905
908
|
) as any
|
|
906
909
|
|
|
907
|
-
const deleteResolver = SqlResolver
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
910
|
+
const deleteResolver = SqlResolver
|
|
911
|
+
.void({
|
|
912
|
+
Request: idSchema,
|
|
913
|
+
execute: (ids: any) => sql`delete from ${sql(options.tableName)} where ${sql.in(idColumn, ids)}`
|
|
914
|
+
})
|
|
915
|
+
.pipe(
|
|
916
|
+
RequestResolver.setDelay(options.window),
|
|
917
|
+
setMaxBatchSize,
|
|
918
|
+
RequestResolver.withSpan(`${options.spanPrefix}.deleteResolver`)
|
|
919
|
+
)
|
|
915
920
|
const deleteExecute = SqlResolver.request(deleteResolver)
|
|
916
921
|
const delete_ = (
|
|
917
922
|
id: S["fields"][Id]["Type"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
|
|
2
2
|
import { type OperationOptionsBase, type ProcessErrorArgs, ServiceBusClient, type ServiceBusMessage, type ServiceBusMessageBatch, type ServiceBusReceivedMessage, type ServiceBusReceiver } from "@azure/service-bus"
|
|
3
|
-
import { Cause, Effect, Exit, FiberSet, Layer, type Scope
|
|
3
|
+
import { Cause, Context, Effect, Exit, FiberSet, Layer, type Scope } from "effect-app"
|
|
4
4
|
import { InfraLogger } from "../logger.js"
|
|
5
5
|
|
|
6
6
|
const withSpanAndLog = (name: string) => <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
@@ -19,7 +19,7 @@ function makeClient(url: string) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export class ServiceBusClientTag
|
|
22
|
-
extends
|
|
22
|
+
extends Context.Opaque<ServiceBusClientTag, ServiceBusClient>()("@services/Client", { make: makeClient })
|
|
23
23
|
{
|
|
24
24
|
static readonly layer = (url: string) => this.toLayer(this.make(url))
|
|
25
25
|
}
|
|
@@ -50,7 +50,7 @@ const makeSender = (name: string) =>
|
|
|
50
50
|
return { name, sendMessages }
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
export class Sender extends
|
|
53
|
+
export class Sender extends Context.Opaque<Sender, {
|
|
54
54
|
name: string
|
|
55
55
|
sendMessages: (
|
|
56
56
|
messages: ServiceBusMessage | ServiceBusMessage[] | ServiceBusMessageBatch,
|
|
@@ -61,12 +61,12 @@ export class Sender extends ServiceMap.Opaque<Sender, {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export const SenderTag = <Id>() => <Key extends string>(queueName: Key) => {
|
|
64
|
-
const tag =
|
|
64
|
+
const tag = Context.Service<Id, Sender>(`ServiceBus.Sender.${queueName}`)
|
|
65
65
|
|
|
66
66
|
return Object.assign(tag, {
|
|
67
67
|
layer: Layer.effect(
|
|
68
68
|
tag,
|
|
69
|
-
Sender.make(queueName).pipe(Effect.map(
|
|
69
|
+
Sender.make(queueName).pipe(Effect.map(Sender.of))
|
|
70
70
|
)
|
|
71
71
|
})
|
|
72
72
|
}
|
|
@@ -163,7 +163,7 @@ const makeReceiver = (name: string) =>
|
|
|
163
163
|
}
|
|
164
164
|
})
|
|
165
165
|
|
|
166
|
-
export class Receiver extends
|
|
166
|
+
export class Receiver extends Context.Opaque<Receiver, {
|
|
167
167
|
name: string
|
|
168
168
|
make: (waitTillEmpty: Effect.Effect<void>) => Effect.Effect<ServiceBusReceiver, never, Scope.Scope>
|
|
169
169
|
makeSession: (
|
|
@@ -180,12 +180,12 @@ export class Receiver extends ServiceMap.Opaque<Receiver, {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
export const ReceiverTag = <Id>() => <Key extends string>(queueName: Key) => {
|
|
183
|
-
const tag =
|
|
183
|
+
const tag = Context.Service<Id, Receiver>(`ServiceBus.Receiver.${queueName}`)
|
|
184
184
|
|
|
185
185
|
return Object.assign(tag, {
|
|
186
186
|
layer: Layer.effect(
|
|
187
187
|
tag,
|
|
188
|
-
makeReceiver(queueName).pipe(Effect.map(
|
|
188
|
+
makeReceiver(queueName).pipe(Effect.map(Receiver.of))
|
|
189
189
|
)
|
|
190
190
|
})
|
|
191
191
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CosmosClient as ComosClient_ } from "@azure/cosmos"
|
|
2
|
-
import { Effect, Layer
|
|
2
|
+
import { Context, Effect, Layer } from "effect-app"
|
|
3
3
|
|
|
4
4
|
const withClient = (url: string) => Effect.sync(() => new ComosClient_(url))
|
|
5
5
|
|
|
6
6
|
export const makeCosmosClient = (url: string, dbName: string) =>
|
|
7
7
|
Effect.map(withClient(url), (x) => ({ db: x.database(dbName) }))
|
|
8
8
|
|
|
9
|
-
export class CosmosClient extends
|
|
9
|
+
export class CosmosClient extends Context.Service<CosmosClient, {
|
|
10
10
|
readonly db: ReturnType<InstanceType<typeof ComosClient_>["database"]>
|
|
11
11
|
}>()("@services/CosmosClient") {}
|
|
12
12
|
|
package/src/adapters/memQueue.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, type Queue
|
|
1
|
+
import { Context, Effect, type Queue } from "effect-app"
|
|
2
2
|
import * as Q from "effect/Queue"
|
|
3
3
|
|
|
4
4
|
const make = Effect
|
|
@@ -16,6 +16,6 @@ const make = Effect
|
|
|
16
16
|
}
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
export class MemQueue extends
|
|
19
|
+
export class MemQueue extends Context.Opaque<MemQueue>()("effect-app/MemQueue", { make }) {
|
|
20
20
|
static readonly Live = this.toLayer(this.make)
|
|
21
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Layer
|
|
1
|
+
import { Context, Effect, Layer } from "effect-app"
|
|
2
2
|
import { MongoClient as MongoClient_ } from "mongodb"
|
|
3
3
|
|
|
4
4
|
// TODO: we should probably share a single client...
|
|
@@ -15,7 +15,7 @@ const withClient = (url: string) =>
|
|
|
15
15
|
|
|
16
16
|
const makeMongoClient = (url: string, dbName?: string) => Effect.map(withClient(url), (x) => ({ db: x.db(dbName) }))
|
|
17
17
|
|
|
18
|
-
export class MongoClient extends
|
|
18
|
+
export class MongoClient extends Context.Service<MongoClient, {
|
|
19
19
|
readonly db: ReturnType<InstanceType<typeof MongoClient_>["db"]>
|
|
20
20
|
}>()("@services/MongoClient") {}
|
|
21
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect, Layer, Option
|
|
1
|
+
import { Context, Data, Effect, Layer, Option } from "effect-app"
|
|
2
2
|
import type { RedisClient as Client } from "redis"
|
|
3
3
|
import Redlock from "redlock"
|
|
4
4
|
|
|
@@ -90,7 +90,7 @@ export const makeRedisClient = (makeClient: () => Client) =>
|
|
|
90
90
|
.pipe(Effect.uninterruptible, Effect.orDie)
|
|
91
91
|
)
|
|
92
92
|
|
|
93
|
-
export class RedisClient extends
|
|
93
|
+
export class RedisClient extends Context.Service<RedisClient, {
|
|
94
94
|
readonly client: Client
|
|
95
95
|
readonly lock: Redlock
|
|
96
96
|
readonly get: (key: string) => Effect.Effect<Option.Option<string>, ConnectionException>
|