@effect-app/infra 4.0.0-beta.11 → 4.0.0-beta.111
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 +729 -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/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 +21 -16
- 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 +2 -4
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +8 -6
- package/dist/QueueMaker/errors.d.ts +1 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +3 -3
- package/dist/QueueMaker/sbqueue.js +3 -3
- 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 +19 -3
- package/dist/Store/ContextMapContainer.d.ts.map +1 -1
- package/dist/Store/ContextMapContainer.js +13 -3
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +136 -68
- 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/SQL/Pg.d.ts +4 -0
- package/dist/Store/SQL/Pg.d.ts.map +1 -0
- package/dist/Store/SQL/Pg.js +186 -0
- package/dist/Store/SQL/query.d.ts +38 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +367 -0
- package/dist/Store/SQL.d.ts +20 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +370 -0
- package/dist/Store/index.d.ts +4 -1
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +12 -2
- package/dist/Store/service.d.ts +11 -5
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +24 -6
- 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/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 +6 -4
- 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/middleware/middleware.d.ts +35 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +39 -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.d.ts +6 -3
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +11 -6
- package/dist/logger.d.ts.map +1 -1
- package/examples/query.ts +30 -26
- package/package.json +32 -18
- package/src/CUPS.ts +2 -2
- package/src/Emailer/service.ts +2 -2
- package/src/MainFiberSet.ts +2 -2
- package/src/Model/Repository/internal/internal.ts +75 -59
- 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 +8 -7
- package/src/QueueMaker/memQueue.ts +2 -2
- package/src/QueueMaker/sbqueue.ts +2 -2
- package/src/RequestContext.ts +4 -4
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos.ts +352 -255
- package/src/Store/Disk.ts +2 -3
- package/src/Store/Memory.ts +4 -4
- package/src/Store/SQL/Pg.ts +328 -0
- package/src/Store/SQL/query.ts +409 -0
- package/src/Store/SQL.ts +686 -0
- package/src/Store/index.ts +15 -1
- package/src/Store/service.ts +26 -7
- 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 +6 -5
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +43 -0
- package/src/api/routing.ts +3 -3
- package/src/api/setupRequest.ts +27 -7
- package/test/contextProvider.test.ts +11 -11
- 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/date-query.test.d.ts.map +1 -0
- 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/dist/sql-store.test.d.ts.map +1 -0
- package/test/fixtures.ts +10 -8
- package/test/query.test.ts +162 -16
- package/test/rawQuery.test.ts +19 -17
- package/test/requires.test.ts +6 -5
- package/test/rpc-multi-middleware.test.ts +72 -3
- package/test/sql-store.test.ts +1064 -0
- package/test/validateSample.test.ts +1 -1
- package/tsconfig.json +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type {} from "effect/Equal"
|
|
3
3
|
import type {} from "effect/Hash"
|
|
4
|
-
import { Array, Chunk, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST,
|
|
4
|
+
import { Array, Chunk, Context, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST, Unify } from "effect-app"
|
|
5
5
|
import { toNonEmptyArray } from "effect-app/Array"
|
|
6
6
|
import { NotFoundError } from "effect-app/client/errors"
|
|
7
7
|
import { flatMapOption } from "effect-app/Effect"
|
|
@@ -55,14 +55,14 @@ export function makeRepoInternal<
|
|
|
55
55
|
|
|
56
56
|
function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
|
|
57
57
|
args: [Evt] extends [never] ? {
|
|
58
|
-
schemaContext?:
|
|
58
|
+
schemaContext?: Context.Context<RCtx>
|
|
59
59
|
makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
|
|
60
60
|
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
61
61
|
partitionValue?: (e?: Encoded) => string
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
: {
|
|
65
|
-
schemaContext?:
|
|
65
|
+
schemaContext?: Context.Context<RCtx>
|
|
66
66
|
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
|
|
67
67
|
makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
|
|
68
68
|
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
@@ -72,12 +72,12 @@ export function makeRepoInternal<
|
|
|
72
72
|
) {
|
|
73
73
|
return Effect
|
|
74
74
|
.gen(function*() {
|
|
75
|
-
const rctx:
|
|
75
|
+
const rctx: Context.Context<RCtx> = args.schemaContext ?? Context.empty() as any
|
|
76
76
|
const provideRctx = Effect.provide(rctx)
|
|
77
77
|
const encodeMany = flow(
|
|
78
78
|
S.encodeEffect(S.Array(schema)),
|
|
79
79
|
provideRctx,
|
|
80
|
-
Effect.withSpan("encodeMany", {}, { captureStackTrace: false })
|
|
80
|
+
Effect.withSpan("encodeMany", { attributes: { itemType: name } }, { captureStackTrace: false })
|
|
81
81
|
)
|
|
82
82
|
const decode = flow(S.decodeEffect(schema), provideRctx)
|
|
83
83
|
const decodeMany = flow(
|
|
@@ -113,11 +113,14 @@ export function makeRepoInternal<
|
|
|
113
113
|
let ast = _.ast
|
|
114
114
|
if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
|
|
115
115
|
|
|
116
|
-
// In v4, to get the encoded (from) side of a schema, use SchemaAST.toEncoded
|
|
117
116
|
const pickIdFromAst = (a: SchemaAST.AST) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// Unwrap Declaration (e.g. TaggedClass) to get the underlying Objects AST
|
|
118
|
+
let inner = a
|
|
119
|
+
if (inner._tag === "Declaration") inner = inner.typeParameters[0]!
|
|
120
|
+
// Pick from the original AST to preserve the full encoding chain (e.g. decodeTo transformations).
|
|
121
|
+
// Using toEncoded would lose transformation info needed to encode Type -> Encoded.
|
|
122
|
+
if (SchemaAST.isObjects(inner)) {
|
|
123
|
+
const field = inner.propertySignatures.find((_) => _.name === idKey)
|
|
121
124
|
if (field) {
|
|
122
125
|
return S.Struct({ [idKey]: S.make(field.type) }) as unknown as Codec<T, Encoded>
|
|
123
126
|
}
|
|
@@ -161,7 +164,7 @@ export function makeRepoInternal<
|
|
|
161
164
|
)
|
|
162
165
|
})
|
|
163
166
|
|
|
164
|
-
const find = Effect.fn("find")(function*(id: T[IdKey]) {
|
|
167
|
+
const find = Effect.fn("find", { attributes: { itemType: name } })(function*(id: T[IdKey]) {
|
|
165
168
|
yield* Effect.annotateCurrentSpan({ itemId: id })
|
|
166
169
|
|
|
167
170
|
return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
|
|
@@ -188,62 +191,71 @@ export function makeRepoInternal<
|
|
|
188
191
|
Effect.andThen(saveAllE)
|
|
189
192
|
)
|
|
190
193
|
|
|
191
|
-
const saveAndPublish = Effect.fn("saveAndPublish"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
194
|
+
const saveAndPublish = Effect.fn("saveAndPublish", { attributes: { itemType: name } })(
|
|
195
|
+
function*(items: Iterable<T>, events: Iterable<Evt> = []) {
|
|
196
|
+
const it = Chunk.fromIterable(items)
|
|
197
|
+
const evts = [...events]
|
|
198
|
+
yield* Effect.annotateCurrentSpan({ itemIds: [...Chunk.map(it, (_) => _[idKey])], events: evts.length })
|
|
199
|
+
return yield* saveAll(it)
|
|
200
|
+
.pipe(
|
|
201
|
+
Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
|
|
202
|
+
// TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
|
|
203
|
+
(_) => flatMapOption(_, pub),
|
|
204
|
+
Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
|
|
205
|
+
Effect.asVoid
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
)
|
|
204
209
|
|
|
205
|
-
const removeAndPublish = Effect.fn("removeAndPublish"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
210
|
+
const removeAndPublish = Effect.fn("removeAndPublish", { attributes: { itemType: name } })(
|
|
211
|
+
function*(a: Iterable<T>, events: Iterable<Evt> = []) {
|
|
212
|
+
const { set } = yield* cms
|
|
213
|
+
const it = [...a]
|
|
214
|
+
const evts = [...events]
|
|
215
|
+
yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
|
|
216
|
+
const items = yield* encodeMany(it).pipe(Effect.orDie)
|
|
217
|
+
if (Array.isReadonlyArrayNonEmpty(items)) {
|
|
218
|
+
yield* store.batchRemove(
|
|
219
|
+
items.map((_) => (_[idKey])),
|
|
220
|
+
args.config?.partitionValue?.(items[0])
|
|
221
|
+
)
|
|
222
|
+
for (const e of items) {
|
|
223
|
+
set(e[idKey], undefined)
|
|
224
|
+
}
|
|
225
|
+
yield* Effect
|
|
226
|
+
.sync(() => toNonEmptyArray(evts))
|
|
227
|
+
// TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
|
|
228
|
+
.pipe((_) => flatMapOption(_, pub))
|
|
223
229
|
|
|
224
|
-
|
|
230
|
+
yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
|
|
231
|
+
}
|
|
225
232
|
}
|
|
226
|
-
|
|
233
|
+
)
|
|
227
234
|
|
|
228
|
-
const removeById = Effect.fn("removeById"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
const removeById = Effect.fn("removeById", { attributes: { itemType: name } })(
|
|
236
|
+
function*(...ids: readonly T[IdKey][]) {
|
|
237
|
+
if (!Array.isReadonlyArrayNonEmpty(ids)) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
const { set } = yield* cms
|
|
241
|
+
const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
|
|
242
|
+
yield* Effect.annotateCurrentSpan({ itemIds: eids })
|
|
243
|
+
yield* store.batchRemove(eids)
|
|
244
|
+
for (const id of eids) {
|
|
245
|
+
set(id, undefined)
|
|
246
|
+
}
|
|
247
|
+
yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
|
|
238
248
|
}
|
|
239
|
-
|
|
240
|
-
})
|
|
249
|
+
)
|
|
241
250
|
|
|
242
251
|
const parseMany = (items: readonly PM[]) =>
|
|
243
252
|
Effect
|
|
244
253
|
.flatMap(cms, (cm) =>
|
|
245
254
|
decodeMany(items.map((_) => mapReverse(_, cm.set)))
|
|
246
|
-
.pipe(
|
|
255
|
+
.pipe(
|
|
256
|
+
Effect.orDie,
|
|
257
|
+
Effect.withSpan("parseMany", { attributes: { itemType: name } }, { captureStackTrace: false })
|
|
258
|
+
))
|
|
247
259
|
const parseMany2 = <A, R>(
|
|
248
260
|
items: readonly PM[],
|
|
249
261
|
schema: S.Codec<A, Encoded, R>
|
|
@@ -254,7 +266,10 @@ export function makeRepoInternal<
|
|
|
254
266
|
.decodeEffect(S.Array(schema))(
|
|
255
267
|
items.map((_) => mapReverse(_, cm.set))
|
|
256
268
|
)
|
|
257
|
-
.pipe(
|
|
269
|
+
.pipe(
|
|
270
|
+
Effect.orDie,
|
|
271
|
+
Effect.withSpan("parseMany2", { attributes: { itemType: name } }, { captureStackTrace: false })
|
|
272
|
+
))
|
|
258
273
|
const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
|
|
259
274
|
store
|
|
260
275
|
.filter(
|
|
@@ -327,6 +342,7 @@ export function makeRepoInternal<
|
|
|
327
342
|
: eff,
|
|
328
343
|
Effect.withSpan("Repository.query [effect-app/infra]", {
|
|
329
344
|
attributes: {
|
|
345
|
+
itemType: name,
|
|
330
346
|
"repository.model_name": name,
|
|
331
347
|
query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
|
|
332
348
|
}
|
|
@@ -334,7 +350,7 @@ export function makeRepoInternal<
|
|
|
334
350
|
)
|
|
335
351
|
}) as any
|
|
336
352
|
|
|
337
|
-
const validateSample = Effect.fn("validateSample")(function*(options?: {
|
|
353
|
+
const validateSample = Effect.fn("validateSample", { attributes: { itemType: name } })(function*(options?: {
|
|
338
354
|
percentage?: number
|
|
339
355
|
maxItems?: number
|
|
340
356
|
}) {
|
|
@@ -441,7 +457,7 @@ export function makeRepoInternal<
|
|
|
441
457
|
// },
|
|
442
458
|
save: (...xes: any[]) =>
|
|
443
459
|
Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
|
|
444
|
-
Effect.withSpan("mapped.save", {}, { captureStackTrace: false })
|
|
460
|
+
Effect.withSpan("mapped.save", { attributes: { itemType: name } }, { captureStackTrace: false })
|
|
445
461
|
)
|
|
446
462
|
}
|
|
447
463
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// import type { ParserEnv } from "effect-app/Schema/custom/Parser"
|
|
8
8
|
import type {} from "effect/Equal"
|
|
9
9
|
import type {} from "effect/Hash"
|
|
10
|
-
import { Effect, type NonEmptyReadonlyArray, type S
|
|
10
|
+
import { type Context, Effect, type NonEmptyReadonlyArray, type S } from "effect-app"
|
|
11
11
|
import type { StoreConfig, StoreMaker } from "../../Store.js"
|
|
12
12
|
import type { FieldValues } from "../filter/types.js"
|
|
13
13
|
import { type ExtendedRepository, extendRepo } from "./ext.js"
|
|
@@ -52,7 +52,7 @@ export interface RepositoryOptions<
|
|
|
52
52
|
* Optional context to be provided to Schema decode/encode.
|
|
53
53
|
* Useful for effectful transformations like XWithItems, where items is a transformation retrieving elements from another database table or other source.
|
|
54
54
|
*/
|
|
55
|
-
schemaContext?:
|
|
55
|
+
schemaContext?: Context.Context<RCtx>
|
|
56
56
|
|
|
57
57
|
overrides?: (
|
|
58
58
|
repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
|
package/src/Operations.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { reportError } from "@effect-app/infra/errorReporter"
|
|
2
2
|
import { subHours } from "date-fns"
|
|
3
|
-
import { Cause, copy, Duration, Effect, Exit, type Fiber, Layer, Option, S, Schedule
|
|
3
|
+
import { Cause, Context, copy, Duration, Effect, Exit, type Fiber, Layer, Option, S, Schedule } from "effect-app"
|
|
4
4
|
import { annotateLogscoped } from "effect-app/Effect"
|
|
5
5
|
import { dual, pipe } from "effect-app/Function"
|
|
6
6
|
import { Operation, OperationFailure, OperationId, type OperationProgress, OperationSuccess } from "effect-app/Operations"
|
|
@@ -189,7 +189,7 @@ const make = Effect.gen(function*() {
|
|
|
189
189
|
}
|
|
190
190
|
})
|
|
191
191
|
|
|
192
|
-
export class Operations extends
|
|
192
|
+
export class Operations extends Context.Opaque<Operations>()("effect-app/Operations", { make }) {
|
|
193
193
|
private static readonly CleanupLive = this
|
|
194
194
|
.use((_) =>
|
|
195
195
|
_.cleanup.pipe(
|
package/src/OperationsRepo.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Context, Effect } from "effect-app"
|
|
2
2
|
import { Operation } from "effect-app/Operations"
|
|
3
3
|
import { makeRepo } from "./Model.js"
|
|
4
4
|
|
|
5
|
-
export class OperationsRepo extends
|
|
5
|
+
export class OperationsRepo extends Context.Service<OperationsRepo>()(
|
|
6
6
|
"OperationRepo",
|
|
7
7
|
{
|
|
8
8
|
make: Effect.gen(function*() {
|
|
@@ -3,13 +3,13 @@ import { reportNonInterruptedFailure } from "@effect-app/infra/QueueMaker/errors
|
|
|
3
3
|
import { type QueueBase, QueueMeta } from "@effect-app/infra/QueueMaker/service"
|
|
4
4
|
import { subMinutes } from "date-fns"
|
|
5
5
|
import { Effect, Fiber, type NonEmptyReadonlyArray, Option, S, Tracer } from "effect-app"
|
|
6
|
-
import type
|
|
6
|
+
import { type NonEmptyString255 } from "effect-app/Schema"
|
|
7
7
|
import { pretty } from "effect-app/utils"
|
|
8
8
|
import { SqlClient } from "effect/unstable/sql"
|
|
9
9
|
import { SQLModel } from "../adapters/SQL.js"
|
|
10
10
|
import { InfraLogger } from "../logger.js"
|
|
11
11
|
|
|
12
|
-
export const QueueId = S.
|
|
12
|
+
export const QueueId = S.Finite.pipe(S.brand("QueueId"))
|
|
13
13
|
export type QueueId = typeof QueueId.Type
|
|
14
14
|
|
|
15
15
|
// TODO: let the model track and Auto Generate versionColumn on every update instead
|
|
@@ -78,14 +78,14 @@ export function makeSQLQueue<
|
|
|
78
78
|
|
|
79
79
|
const q = {
|
|
80
80
|
offer: Effect.fnUntraced(function*(body: Evt, meta: typeof QueueMeta.Type) {
|
|
81
|
-
yield* queueRepo.insertVoid({
|
|
81
|
+
yield* queueRepo.insertVoid(Queue.insert.make({
|
|
82
82
|
body,
|
|
83
83
|
meta,
|
|
84
84
|
name: queueName,
|
|
85
85
|
processingAt: Option.none(),
|
|
86
86
|
finishedAt: Option.none(),
|
|
87
87
|
etag: crypto.randomUUID()
|
|
88
|
-
})
|
|
88
|
+
}))
|
|
89
89
|
}),
|
|
90
90
|
take: Effect.gen(function*() {
|
|
91
91
|
while (true) {
|
|
@@ -94,15 +94,16 @@ export function makeSQLQueue<
|
|
|
94
94
|
const dec = yield* decodeDrain(first)
|
|
95
95
|
const { createdAt, updatedAt, ...rest } = dec
|
|
96
96
|
return yield* drainRepo.update(
|
|
97
|
-
{ ...rest, processingAt: Option.some(new Date()) } // auto in lib , etag: crypto.randomUUID()
|
|
97
|
+
Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
|
|
98
98
|
)
|
|
99
99
|
}
|
|
100
100
|
if (first) return first
|
|
101
101
|
yield* Effect.sleep(250)
|
|
102
102
|
}
|
|
103
103
|
}),
|
|
104
|
-
finish: ({ createdAt, updatedAt, ...q }: Drain)
|
|
105
|
-
drainRepo.updateVoid({ ...q, finishedAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
|
|
104
|
+
finish: Effect.fn(function*({ createdAt, updatedAt, ...q }: Drain) {
|
|
105
|
+
return yield* drainRepo.updateVoid(Drain.update.make({ ...q, finishedAt: Option.some(new Date()) })) // auto in lib , etag: crypto.randomUUID()
|
|
106
|
+
})
|
|
106
107
|
}
|
|
107
108
|
const queue = {
|
|
108
109
|
publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
|
|
@@ -25,10 +25,10 @@ export function makeMemQueue<
|
|
|
25
25
|
const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
|
|
26
26
|
|
|
27
27
|
const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
|
|
28
|
-
const wireSchemaJson = S.fromJsonString(wireSchema)
|
|
28
|
+
const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
|
|
29
29
|
const encodePublish = S.encodeEffect(wireSchemaJson)
|
|
30
30
|
const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
|
|
31
|
-
const drainWJson = S.fromJsonString(drainW)
|
|
31
|
+
const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
|
|
32
32
|
|
|
33
33
|
const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
|
|
34
34
|
|
|
@@ -21,10 +21,10 @@ export function makeServiceBusQueue<
|
|
|
21
21
|
body: schema,
|
|
22
22
|
meta: QueueMeta
|
|
23
23
|
})
|
|
24
|
-
const wireSchemaJson = S.fromJsonString(wireSchema)
|
|
24
|
+
const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
|
|
25
25
|
const encodePublish = S.encodeEffect(wireSchemaJson)
|
|
26
26
|
const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
|
|
27
|
-
const drainWJson = S.fromJsonString(drainW)
|
|
27
|
+
const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
|
|
28
28
|
const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
|
|
29
29
|
|
|
30
30
|
return Effect.gen(function*() {
|
package/src/RequestContext.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Context, S } from "effect-app"
|
|
2
2
|
import { UserProfileId } from "effect-app/ids"
|
|
3
3
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
4
4
|
|
|
5
|
-
export const Locale = S.
|
|
5
|
+
export const Locale = S.Literals(["en", "de"])
|
|
6
6
|
export type Locale = typeof Locale.Type
|
|
7
7
|
|
|
8
|
-
export class LocaleRef extends
|
|
8
|
+
export class LocaleRef extends Context.Reference("Locale", { defaultValue: (): Locale => "en" }) {}
|
|
9
9
|
|
|
10
10
|
export class RequestContext extends S.ExtendedClass<
|
|
11
11
|
RequestContext,
|
|
@@ -23,7 +23,7 @@ export class RequestContext extends S.ExtendedClass<
|
|
|
23
23
|
/** @deprecated */
|
|
24
24
|
userProfile: S.optional(S.Struct({ sub: UserProfileId })) //
|
|
25
25
|
}) {
|
|
26
|
-
// static Tag =
|
|
26
|
+
// static Tag = Context.Tag<RequestContext>()
|
|
27
27
|
|
|
28
28
|
static toMonitoring(this: void, self: RequestContext) {
|
|
29
29
|
return {
|
package/src/RequestFiberSet.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { Effect, Fiber, FiberSet, Layer,
|
|
2
|
+
import { Context, Effect, Fiber, FiberSet, Layer, Option, type Tracer } from "effect-app"
|
|
3
3
|
import { reportRequestError, reportUnknownRequestError } from "./api/reportError.js"
|
|
4
4
|
import { InfraLogger } from "./logger.js"
|
|
5
5
|
|
|
@@ -8,8 +8,8 @@ const getRootParentSpan = Effect.gen(function*() {
|
|
|
8
8
|
Effect.catchTag("NoSuchElementError", () => Effect.succeed(null))
|
|
9
9
|
)
|
|
10
10
|
if (!span) return span
|
|
11
|
-
while (span._tag === "Span" && span.parent
|
|
12
|
-
span = span.parent
|
|
11
|
+
while (span._tag === "Span" && Option.isSome(span.parent)) {
|
|
12
|
+
span = span.parent.value
|
|
13
13
|
}
|
|
14
14
|
return span
|
|
15
15
|
})
|
|
@@ -92,7 +92,7 @@ const make = Effect.gen(function*() {
|
|
|
92
92
|
* Whenever you fork a fiber for a Request, and you want to prevent dependent services to close prematurely on interruption,
|
|
93
93
|
* like the ServiceBus Sender, you should register these fibers in this FiberSet.
|
|
94
94
|
*/
|
|
95
|
-
export class RequestFiberSet extends
|
|
95
|
+
export class RequestFiberSet extends Context.Service<RequestFiberSet>()("RequestFiberSet", { make }) {
|
|
96
96
|
static readonly Live = Layer.effect(this, this.make)
|
|
97
97
|
static readonly register = <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
98
98
|
this.asEffect().pipe(Effect.andThen((_) => _.register(self)))
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Data, Effect, Layer,
|
|
1
|
+
import { Context, Data, Effect, Layer, RequestResolver } from "effect-app"
|
|
2
|
+
import { dual } from "effect/Function"
|
|
3
|
+
import type * as Request from "effect/Request"
|
|
2
4
|
import { ContextMap } from "./service.js"
|
|
3
5
|
|
|
4
6
|
// TODO: we have to create a new contextmap on every request.
|
|
@@ -7,7 +9,7 @@ import { ContextMap } from "./service.js"
|
|
|
7
9
|
// we can call another start after startup. but it would be even better if we could Die on accessing rootmap
|
|
8
10
|
// we could also make the ContextMap optional, and when missing, issue a warning instead?
|
|
9
11
|
|
|
10
|
-
export class ContextMapContainer extends
|
|
12
|
+
export class ContextMapContainer extends Context.Reference("ContextMapContainer", {
|
|
11
13
|
defaultValue: (): ContextMap | "root" => "root"
|
|
12
14
|
}) {
|
|
13
15
|
static readonly layer = Layer.effect(this, ContextMap.make.pipe(Effect.map(ContextMap.of)))
|
|
@@ -18,3 +20,40 @@ export class ContextMapNotStartedError extends Data.TaggedError("ContextMapNotSt
|
|
|
18
20
|
export const getContextMap = ContextMapContainer.asEffect().pipe(
|
|
19
21
|
Effect.filterOrFail((_) => _ !== "root", () => new ContextMapNotStartedError())
|
|
20
22
|
)
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Uses the official `RequestResolver.withCache` internally,
|
|
26
|
+
* creating one cached resolver per ContextMap (i.e. per request).
|
|
27
|
+
* Uses a shared semaphore in the ContextMap to ensure safe single initialization.
|
|
28
|
+
*/
|
|
29
|
+
export const withRequestResolverCache: {
|
|
30
|
+
<A extends Request.Request<any, any>>(options: {
|
|
31
|
+
readonly capacity: number
|
|
32
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
33
|
+
}): (
|
|
34
|
+
self: RequestResolver.RequestResolver<A>
|
|
35
|
+
) => Effect.Effect<RequestResolver.RequestResolver<A>, ContextMapNotStartedError>
|
|
36
|
+
<A extends Request.Request<any, any>>(
|
|
37
|
+
self: RequestResolver.RequestResolver<A>,
|
|
38
|
+
options: {
|
|
39
|
+
readonly capacity: number
|
|
40
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
41
|
+
}
|
|
42
|
+
): Effect.Effect<RequestResolver.RequestResolver<A>, ContextMapNotStartedError>
|
|
43
|
+
} = dual(2, <A extends Request.Request<any, any>>(
|
|
44
|
+
self: RequestResolver.RequestResolver<A>,
|
|
45
|
+
options: {
|
|
46
|
+
readonly capacity: number
|
|
47
|
+
readonly strategy?: "lru" | "fifo" | undefined
|
|
48
|
+
}
|
|
49
|
+
): Effect.Effect<RequestResolver.RequestResolver<A>, ContextMapNotStartedError> => {
|
|
50
|
+
const cacheKey = Symbol()
|
|
51
|
+
return getContextMap.pipe(
|
|
52
|
+
Effect.flatMap((ctxMap) =>
|
|
53
|
+
ctxMap.getOrCreateStoreEffect(
|
|
54
|
+
cacheKey,
|
|
55
|
+
RequestResolver.withCache(self, options)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
})
|