@effect-app/infra 4.0.0-beta.13 → 4.0.0-beta.131
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 +864 -0
- package/dist/CUPS.d.ts +13 -5
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +10 -12
- package/dist/Emailer/service.d.ts +2 -2
- 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/Registry.d.ts +20 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +17 -0
- package/dist/Model/Repository/ext.d.ts +21 -3
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +54 -2
- package/dist/Model/Repository/internal/internal.d.ts +4 -4
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +31 -21
- package/dist/Model/Repository/makeRepo.d.ts +6 -5
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +4 -1
- package/dist/Model/Repository/service.d.ts +27 -22
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Model/Repository/validation.d.ts +59 -9
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/Repository.d.ts +1 -0
- package/dist/Model/Repository.d.ts.map +1 -1
- package/dist/Model/Repository.js +2 -1
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
- package/dist/Model/query/new-kid-interpreter.js +3 -3
- package/dist/Model.d.ts +1 -0
- package/dist/Model.d.ts.map +1 -1
- package/dist/Model.js +2 -1
- package/dist/Operations.d.ts +4 -4
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +56 -59
- package/dist/OperationsRepo.d.ts +4 -4
- package/dist/OperationsRepo.d.ts.map +1 -1
- package/dist/OperationsRepo.js +3 -3
- package/dist/QueueMaker/SQLQueue.d.ts +3 -6
- package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/QueueMaker/SQLQueue.js +105 -114
- package/dist/QueueMaker/errors.d.ts +1 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.d.ts +6 -3
- package/dist/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/QueueMaker/memQueue.js +51 -62
- package/dist/QueueMaker/sbqueue.d.ts +5 -2
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +36 -52
- package/dist/RequestContext.d.ts +51 -21
- 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 +18 -2
- 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 +308 -242
- package/dist/Store/Disk.d.ts +1 -1
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +25 -22
- package/dist/Store/Memory.d.ts +3 -3
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +27 -22
- 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 +189 -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 +381 -0
- package/dist/Store/index.d.ts +4 -1
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +15 -3
- package/dist/Store/service.d.ts +16 -5
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +24 -6
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +3 -4
- package/dist/adapters/ServiceBus.d.ts +6 -6
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +13 -15
- 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 +2 -2
- 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 +42 -4
- package/dist/api/internal/auth.d.ts.map +1 -1
- package/dist/api/internal/auth.js +160 -29
- 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 +11 -7
- 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 +37 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware.js +45 -14
- package/dist/api/routing.d.ts +4 -6
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +13 -6
- 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/errorReporter.d.ts +3 -3
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +16 -23
- package/dist/logger.d.ts.map +1 -1
- package/dist/rateLimit.d.ts +8 -2
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +5 -11
- package/examples/query.ts +30 -26
- package/package.json +36 -22
- package/src/CUPS.ts +9 -11
- package/src/Emailer/service.ts +2 -2
- package/src/MainFiberSet.ts +2 -2
- package/src/Model/Repository/Registry.ts +33 -0
- package/src/Model/Repository/ext.ts +93 -6
- package/src/Model/Repository/internal/internal.ts +84 -76
- package/src/Model/Repository/makeRepo.ts +11 -8
- package/src/Model/Repository/service.ts +31 -22
- package/src/Model/Repository.ts +1 -0
- package/src/Model/query/new-kid-interpreter.ts +2 -2
- package/src/Model.ts +1 -0
- package/src/Operations.ts +78 -113
- package/src/OperationsRepo.ts +2 -2
- package/src/QueueMaker/SQLQueue.ts +121 -151
- package/src/QueueMaker/memQueue.ts +82 -103
- package/src/QueueMaker/sbqueue.ts +55 -85
- package/src/RequestContext.ts +4 -4
- package/src/RequestFiberSet.ts +4 -4
- package/src/Store/ContextMapContainer.ts +41 -2
- package/src/Store/Cosmos.ts +437 -343
- package/src/Store/Disk.ts +52 -49
- package/src/Store/Memory.ts +54 -48
- package/src/Store/SQL/Pg.ts +318 -0
- package/src/Store/SQL/query.ts +409 -0
- package/src/Store/SQL.ts +668 -0
- package/src/Store/index.ts +17 -2
- package/src/Store/service.ts +31 -7
- package/src/Store/utils.ts +23 -22
- package/src/adapters/ServiceBus.ts +111 -115
- 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/auth.ts +246 -44
- package/src/api/internal/events.ts +14 -9
- package/src/api/layerUtils.ts +8 -8
- package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
- package/src/api/routing/middleware/middleware.ts +52 -12
- package/src/api/routing.ts +17 -7
- package/src/api/setupRequest.ts +27 -7
- package/src/errorReporter.ts +58 -72
- package/src/rateLimit.ts +30 -22
- package/test/auth.test.ts +101 -0
- package/test/contextProvider.test.ts +11 -11
- package/test/controller.test.ts +18 -13
- package/test/dist/auth.test.d.ts.map +1 -0
- 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 +19 -9
- 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/repository-ext.test.d.ts.map +1 -0
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
- 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 +209 -31
- package/test/rawQuery.test.ts +23 -19
- package/test/repository-ext.test.ts +58 -0
- package/test/requires.test.ts +6 -5
- package/test/routing-interruptibility.test.ts +63 -0
- package/test/rpc-multi-middleware.test.ts +72 -3
- package/test/sql-store.test.ts +1064 -0
- package/test/validateSample.test.ts +12 -9
- package/tsconfig.json +0 -1
package/src/CUPS.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FileOptions, tempFile } from "@effect-app/infra/fileUtil"
|
|
2
2
|
import cp from "child_process"
|
|
3
|
-
import { Config, Effect, Layer, Option, Predicate, S
|
|
3
|
+
import { Config, Context, Effect, Layer, Option, Predicate, S } from "effect-app"
|
|
4
4
|
import { pretty } from "effect-app/utils"
|
|
5
5
|
import fs from "fs"
|
|
6
6
|
import os from "os"
|
|
@@ -74,15 +74,13 @@ function printBuffer(printer: PrinterConfig, options: string[]) {
|
|
|
74
74
|
)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
})
|
|
85
|
-
}
|
|
77
|
+
const getAvailablePrinters = Effect.fnUntraced(function*(host?: string) {
|
|
78
|
+
const { stdout } = yield* exec(["lpstat", ...buildListArgs({ host }), "-s"].join(" "))
|
|
79
|
+
return [...stdout.matchAll(/device for (\w+):/g)]
|
|
80
|
+
.map((_) => _[1])
|
|
81
|
+
.filter(Predicate.isNotNullish)
|
|
82
|
+
.map((_) => S.NonEmptyString255(_))
|
|
83
|
+
})
|
|
86
84
|
|
|
87
85
|
function* buildListArgs(config?: { host?: string | undefined }) {
|
|
88
86
|
if (config?.host) {
|
|
@@ -100,7 +98,7 @@ export const CUPSConfig = Config.all({
|
|
|
100
98
|
)
|
|
101
99
|
})
|
|
102
100
|
|
|
103
|
-
export class CUPS extends
|
|
101
|
+
export class CUPS extends Context.Service<CUPS>()("effect-app/CUPS", {
|
|
104
102
|
make: Effect.gen(function*() {
|
|
105
103
|
const config = yield* CUPSConfig
|
|
106
104
|
const serverUrl = Option.getOrUndefined(config.server)
|
package/src/Emailer/service.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { MailContent, MailData } from "@sendgrid/helpers/classes/mail.js"
|
|
2
2
|
import type { ResponseError } from "@sendgrid/mail"
|
|
3
|
-
import { Data, type Effect, type NonEmptyReadonlyArray, type Redacted
|
|
3
|
+
import { Context, Data, type Effect, type NonEmptyReadonlyArray, type Redacted } from "effect-app"
|
|
4
4
|
import type { Email } from "effect-app/Schema"
|
|
5
5
|
|
|
6
6
|
export class SendMailError extends Data.TaggedError("SendMailError")<{
|
|
7
7
|
readonly raw: Error | ResponseError
|
|
8
8
|
}> {}
|
|
9
9
|
|
|
10
|
-
export class Emailer extends
|
|
10
|
+
export class Emailer extends Context.Opaque<Emailer, {
|
|
11
11
|
sendMail: (msg: EmailMsgOptionalFrom) => Effect.Effect<void, SendMailError>
|
|
12
12
|
}>()("effect-app/Emailer") {}
|
|
13
13
|
|
package/src/MainFiberSet.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Fiber, FiberSet, Layer
|
|
1
|
+
import { Context, Effect, Fiber, FiberSet, Layer } from "effect-app"
|
|
2
2
|
import type {} from "effect/Scope"
|
|
3
3
|
import { InfraLogger } from "./logger.js"
|
|
4
4
|
import { reportNonInterruptedFailureCause } from "./QueueMaker/errors.js"
|
|
@@ -62,7 +62,7 @@ const make = Effect.gen(function*() {
|
|
|
62
62
|
* you should register these long running fibers in a FiberSet, and join them at the end of your main program.
|
|
63
63
|
* This way any errors will blow up the main program instead of fibers dying unknowingly.
|
|
64
64
|
*/
|
|
65
|
-
export class MainFiberSet extends
|
|
65
|
+
export class MainFiberSet extends Context.Service<MainFiberSet>()("MainFiberSet", { make }) {
|
|
66
66
|
static readonly Live = Layer.effect(this, this.make)
|
|
67
67
|
static readonly JoinLive = this.asEffect().pipe(
|
|
68
68
|
Effect.andThen((_) => _.join),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Context, Effect } from "effect-app"
|
|
2
|
+
|
|
3
|
+
export interface RegisteredRepository {
|
|
4
|
+
readonly seedNamespace: (namespace: string) => Effect.Effect<void>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const make = Effect.sync(() => {
|
|
8
|
+
const repos = new Map<string, RegisteredRepository>()
|
|
9
|
+
return {
|
|
10
|
+
register(modelName: string, repo: RegisteredRepository) {
|
|
11
|
+
repos.set(modelName, repo)
|
|
12
|
+
},
|
|
13
|
+
seedNamespace: (namespace: string) =>
|
|
14
|
+
Effect.suspend(() =>
|
|
15
|
+
Effect.forEach(
|
|
16
|
+
repos.values(),
|
|
17
|
+
(r) => r.seedNamespace(namespace),
|
|
18
|
+
{ concurrency: "unbounded", discard: true }
|
|
19
|
+
)
|
|
20
|
+
),
|
|
21
|
+
get entries(): ReadonlyMap<string, RegisteredRepository> {
|
|
22
|
+
return repos
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export class RepositoryRegistry extends Context.Opaque<RepositoryRegistry, {
|
|
28
|
+
readonly register: (modelName: string, repo: RegisteredRepository) => void
|
|
29
|
+
readonly seedNamespace: (namespace: string) => Effect.Effect<void>
|
|
30
|
+
readonly entries: ReadonlyMap<string, RegisteredRepository>
|
|
31
|
+
}>()("effect-app/RepositoryRegistry", { make }) {}
|
|
32
|
+
|
|
33
|
+
export const RepositoryRegistryLive = RepositoryRegistry.toLayer(RepositoryRegistry.make)
|
|
@@ -9,6 +9,22 @@ import type { Query, QueryEnd, QueryWhere } from "../query.js"
|
|
|
9
9
|
import * as Q from "../query.js"
|
|
10
10
|
import type { Repository } from "./service.js"
|
|
11
11
|
|
|
12
|
+
interface BatchOptions {
|
|
13
|
+
readonly batch?: true | number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const asReadonlyArray = <T>(itemOrItems: T | ReadonlyArray<T>): ReadonlyArray<T> =>
|
|
17
|
+
globalThis.Array.isArray(itemOrItems)
|
|
18
|
+
? itemOrItems as ReadonlyArray<T>
|
|
19
|
+
: [itemOrItems as T]
|
|
20
|
+
|
|
21
|
+
const getBatchSize = (batch?: true | number) =>
|
|
22
|
+
batch === true
|
|
23
|
+
? 100
|
|
24
|
+
: typeof batch === "number" && Number.isFinite(batch) && batch > 0
|
|
25
|
+
? Math.floor(batch)
|
|
26
|
+
: undefined
|
|
27
|
+
|
|
12
28
|
export const extendRepo = <
|
|
13
29
|
T,
|
|
14
30
|
Encoded extends FieldValues,
|
|
@@ -16,9 +32,10 @@ export const extendRepo = <
|
|
|
16
32
|
ItemType extends string,
|
|
17
33
|
IdKey extends keyof T & keyof Encoded,
|
|
18
34
|
RSchema,
|
|
19
|
-
RPublish
|
|
35
|
+
RPublish,
|
|
36
|
+
RProvided = never
|
|
20
37
|
>(
|
|
21
|
-
repo: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>
|
|
38
|
+
repo: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided>
|
|
22
39
|
) => {
|
|
23
40
|
const get = (id: T[IdKey]) =>
|
|
24
41
|
repo.find(id).pipe(
|
|
@@ -244,8 +261,77 @@ export const extendRepo = <
|
|
|
244
261
|
request: (id: T[IdKey]) => Effect.request(_request({ id }), requestResolver),
|
|
245
262
|
get,
|
|
246
263
|
log: (evt: Evt) => AnyPureDSL.log(evt),
|
|
247
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Enables chunked writes for large batches via `options.batch`.
|
|
266
|
+
* Note: batching breaks transactional properties because chunks are saved independently.
|
|
267
|
+
*/
|
|
268
|
+
save: ((itemOrItems: T | ReadonlyArray<T>, options?: BatchOptions) => {
|
|
269
|
+
const items = asReadonlyArray(itemOrItems)
|
|
270
|
+
if (!Array.isReadonlyArrayNonEmpty(items)) {
|
|
271
|
+
return Effect.void
|
|
272
|
+
}
|
|
273
|
+
const batchSize = getBatchSize(options?.batch)
|
|
274
|
+
if (batchSize === undefined) {
|
|
275
|
+
return repo.saveAndPublish(items)
|
|
276
|
+
}
|
|
277
|
+
return Effect.forEach(
|
|
278
|
+
Array.chunksOf(items, batchSize),
|
|
279
|
+
(batch) => repo.saveAndPublish(batch),
|
|
280
|
+
{ discard: true }
|
|
281
|
+
)
|
|
282
|
+
}) as (
|
|
283
|
+
itemOrItems: T | ReadonlyArray<T>,
|
|
284
|
+
options?: BatchOptions
|
|
285
|
+
) => Effect.Effect<
|
|
286
|
+
void,
|
|
287
|
+
InvalidStateError | OptimisticConcurrencyException,
|
|
288
|
+
RSchema | RPublish
|
|
289
|
+
>,
|
|
248
290
|
saveWithEvents: (events: Iterable<Evt>) => (...items: NonEmptyArray<T>) => repo.saveAndPublish(items, events),
|
|
291
|
+
/**
|
|
292
|
+
* Enables chunked deletes for large batches via `options.batch`.
|
|
293
|
+
* Note: batching breaks transactional properties because chunks are removed independently.
|
|
294
|
+
*/
|
|
295
|
+
remove: ((itemOrItems: T | ReadonlyArray<T>, options?: BatchOptions) => {
|
|
296
|
+
const items = asReadonlyArray(itemOrItems)
|
|
297
|
+
if (!Array.isReadonlyArrayNonEmpty(items)) {
|
|
298
|
+
return Effect.void
|
|
299
|
+
}
|
|
300
|
+
const batchSize = getBatchSize(options?.batch)
|
|
301
|
+
if (batchSize === undefined) {
|
|
302
|
+
return repo.removeAndPublish(items)
|
|
303
|
+
}
|
|
304
|
+
return Effect.forEach(
|
|
305
|
+
Array.chunksOf(items, batchSize),
|
|
306
|
+
(batch) => repo.removeAndPublish(batch),
|
|
307
|
+
{ discard: true }
|
|
308
|
+
)
|
|
309
|
+
}) as (
|
|
310
|
+
itemOrItems: T | ReadonlyArray<T>,
|
|
311
|
+
options?: BatchOptions
|
|
312
|
+
) => Effect.Effect<void, never, RSchema | RPublish>,
|
|
313
|
+
/**
|
|
314
|
+
* Enables chunked deletes for large batches via `options.batch`.
|
|
315
|
+
* Note: batching breaks transactional properties because chunks are removed independently.
|
|
316
|
+
*/
|
|
317
|
+
removeById: ((idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>, options?: BatchOptions) => {
|
|
318
|
+
const ids = asReadonlyArray(idOrIds)
|
|
319
|
+
if (!Array.isReadonlyArrayNonEmpty(ids)) {
|
|
320
|
+
return Effect.void
|
|
321
|
+
}
|
|
322
|
+
const batchSize = getBatchSize(options?.batch)
|
|
323
|
+
if (batchSize === undefined) {
|
|
324
|
+
return repo.removeById(ids)
|
|
325
|
+
}
|
|
326
|
+
return Effect.forEach(
|
|
327
|
+
Array.chunksOf(ids, batchSize),
|
|
328
|
+
(batch) => repo.removeById(batch),
|
|
329
|
+
{ discard: true }
|
|
330
|
+
)
|
|
331
|
+
}) as (
|
|
332
|
+
idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>,
|
|
333
|
+
options?: BatchOptions
|
|
334
|
+
) => Effect.Effect<void, never, RSchema>,
|
|
249
335
|
queryAndSavePure,
|
|
250
336
|
saveManyWithPure,
|
|
251
337
|
byIdAndSaveWithPure,
|
|
@@ -268,7 +354,7 @@ export const extendRepo = <
|
|
|
268
354
|
return {
|
|
269
355
|
...repo,
|
|
270
356
|
...exts
|
|
271
|
-
} as Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish> & typeof exts
|
|
357
|
+
} as Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided> & typeof exts
|
|
272
358
|
}
|
|
273
359
|
|
|
274
360
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
@@ -279,5 +365,6 @@ export interface ExtendedRepository<
|
|
|
279
365
|
ItemType extends string,
|
|
280
366
|
IdKey extends keyof T & keyof Encoded,
|
|
281
367
|
RSchema,
|
|
282
|
-
RPublish
|
|
283
|
-
|
|
368
|
+
RPublish,
|
|
369
|
+
RProvided = never
|
|
370
|
+
> extends ReturnType<typeof extendRepo<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided>> {}
|
|
@@ -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,73 +191,76 @@ 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*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
|
|
237
|
+
const ids = globalThis.Array.isArray(idOrIds)
|
|
238
|
+
? idOrIds as readonly T[IdKey][]
|
|
239
|
+
: [idOrIds as T[IdKey]]
|
|
240
|
+
if (!Array.isReadonlyArrayNonEmpty(ids)) {
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
const { set } = yield* cms
|
|
244
|
+
const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
|
|
245
|
+
yield* Effect.annotateCurrentSpan({ itemIds: eids })
|
|
246
|
+
yield* store.batchRemove(eids)
|
|
247
|
+
for (const id of eids) {
|
|
248
|
+
set(id, undefined)
|
|
249
|
+
}
|
|
250
|
+
yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
|
|
238
251
|
}
|
|
239
|
-
|
|
240
|
-
})
|
|
252
|
+
)
|
|
241
253
|
|
|
242
|
-
const parseMany = (items: readonly PM[])
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.flatMap(cms, (cm) =>
|
|
253
|
-
S
|
|
254
|
-
.decodeEffect(S.Array(schema))(
|
|
255
|
-
items.map((_) => mapReverse(_, cm.set))
|
|
256
|
-
)
|
|
257
|
-
.pipe(Effect.orDie, Effect.withSpan("parseMany2", {}, { captureStackTrace: false })))
|
|
254
|
+
const parseMany = Effect.fn("parseMany", { attributes: { itemType: name } })(function*(items: readonly PM[]) {
|
|
255
|
+
const cm = yield* cms
|
|
256
|
+
return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
|
|
257
|
+
})
|
|
258
|
+
const parseMany2 = Effect.fn("parseMany2", { attributes: { itemType: name } })(
|
|
259
|
+
function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
|
|
260
|
+
const cm = yield* cms
|
|
261
|
+
return yield* S.decodeEffect(S.Array(schema))(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
|
|
262
|
+
}
|
|
263
|
+
)
|
|
258
264
|
const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
|
|
259
265
|
store
|
|
260
266
|
.filter(
|
|
@@ -276,10 +282,10 @@ export function makeRepoInternal<
|
|
|
276
282
|
const query: {
|
|
277
283
|
<A, R, From extends FieldValues>(
|
|
278
284
|
q: Q.QueryProjection<Encoded extends From ? From : never, A, R>
|
|
279
|
-
): Effect.Effect<readonly A[], S.SchemaError, R
|
|
285
|
+
): Effect.Effect<readonly A[], S.SchemaError, Exclude<R, RCtx>>
|
|
280
286
|
<A, R, EncodedRefined extends Encoded = Encoded>(
|
|
281
287
|
q: Q.QAll<NoInfer<Encoded>, NoInfer<EncodedRefined>, A, R>
|
|
282
|
-
): Effect.Effect<readonly A[], never, R
|
|
288
|
+
): Effect.Effect<readonly A[], never, Exclude<R, RCtx>>
|
|
283
289
|
} = (<A, R, EncodedRefined extends Encoded = Encoded>(q: Q.QAll<Encoded, EncodedRefined, A, R>) => {
|
|
284
290
|
const a = Q.toFilter(q)
|
|
285
291
|
const eff = a.mode === "project"
|
|
@@ -327,6 +333,7 @@ export function makeRepoInternal<
|
|
|
327
333
|
: eff,
|
|
328
334
|
Effect.withSpan("Repository.query [effect-app/infra]", {
|
|
329
335
|
attributes: {
|
|
336
|
+
itemType: name,
|
|
330
337
|
"repository.model_name": name,
|
|
331
338
|
query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
|
|
332
339
|
}
|
|
@@ -334,7 +341,7 @@ export function makeRepoInternal<
|
|
|
334
341
|
)
|
|
335
342
|
}) as any
|
|
336
343
|
|
|
337
|
-
const validateSample = Effect.fn("validateSample")(function*(options?: {
|
|
344
|
+
const validateSample = Effect.fn("validateSample", { attributes: { itemType: name } })(function*(options?: {
|
|
338
345
|
percentage?: number
|
|
339
346
|
maxItems?: number
|
|
340
347
|
}) {
|
|
@@ -401,6 +408,7 @@ export function makeRepoInternal<
|
|
|
401
408
|
saveAndPublish,
|
|
402
409
|
removeAndPublish,
|
|
403
410
|
removeById,
|
|
411
|
+
seedNamespace: (namespace: string) => store.seedNamespace(namespace),
|
|
404
412
|
validateSample,
|
|
405
413
|
queryRaw<A, Out, QR>(schema: S.Codec<A, Out, QR>, q: Q.RawQuery<Encoded, Out>) {
|
|
406
414
|
const dec = S.decodeEffect(S.Array(schema))
|
|
@@ -441,12 +449,12 @@ export function makeRepoInternal<
|
|
|
441
449
|
// },
|
|
442
450
|
save: (...xes: any[]) =>
|
|
443
451
|
Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
|
|
444
|
-
Effect.withSpan("mapped.save", {}, { captureStackTrace: false })
|
|
452
|
+
Effect.withSpan("mapped.save", { attributes: { itemType: name } }, { captureStackTrace: false })
|
|
445
453
|
)
|
|
446
454
|
}
|
|
447
455
|
}
|
|
448
456
|
}
|
|
449
|
-
return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish>
|
|
457
|
+
return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish, RCtx>
|
|
450
458
|
})
|
|
451
459
|
.pipe(Effect
|
|
452
460
|
// .withSpan("Repository.make [effect-app/infra]", { attributes: { "repository.model_name": name } })
|
|
@@ -7,11 +7,12 @@
|
|
|
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"
|
|
14
14
|
import { makeRepoInternal } from "./internal/internal.js"
|
|
15
|
+
import { RepositoryRegistry } from "./Registry.js"
|
|
15
16
|
import type { Repository } from "./service.js"
|
|
16
17
|
|
|
17
18
|
export interface RepositoryOptions<
|
|
@@ -52,11 +53,11 @@ export interface RepositoryOptions<
|
|
|
52
53
|
* Optional context to be provided to Schema decode/encode.
|
|
53
54
|
* Useful for effectful transformations like XWithItems, where items is a transformation retrieving elements from another database table or other source.
|
|
54
55
|
*/
|
|
55
|
-
schemaContext?:
|
|
56
|
+
schemaContext?: Context.Context<RCtx>
|
|
56
57
|
|
|
57
58
|
overrides?: (
|
|
58
|
-
repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
|
|
59
|
-
) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
|
|
59
|
+
repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
|
|
60
|
+
) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -83,9 +84,9 @@ export const makeRepo: {
|
|
|
83
84
|
schema: S.Codec<T, Encoded, RSchema>,
|
|
84
85
|
options: RepositoryOptions<IdKey, Encoded, T, ItemType, Evt, RPublish, E, RInitial, RCtx, RSchema>
|
|
85
86
|
): Effect.Effect<
|
|
86
|
-
ExtendedRepository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>,
|
|
87
|
+
ExtendedRepository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>,
|
|
87
88
|
E,
|
|
88
|
-
RInitial | StoreMaker
|
|
89
|
+
RInitial | StoreMaker | RepositoryRegistry
|
|
89
90
|
>
|
|
90
91
|
<
|
|
91
92
|
ItemType extends string,
|
|
@@ -102,9 +103,9 @@ export const makeRepo: {
|
|
|
102
103
|
schema: S.Codec<T, Encoded, RSchema>,
|
|
103
104
|
options: Omit<RepositoryOptions<"id", Encoded, T, ItemType, Evt, RPublish, E, RInitial, RCtx, RSchema>, "idKey">
|
|
104
105
|
): Effect.Effect<
|
|
105
|
-
ExtendedRepository<T, Encoded, Evt, ItemType, "id", Exclude<RSchema, RCtx>, RPublish>,
|
|
106
|
+
ExtendedRepository<T, Encoded, Evt, ItemType, "id", Exclude<RSchema, RCtx>, RPublish, RCtx>,
|
|
106
107
|
E,
|
|
107
|
-
RInitial | StoreMaker
|
|
108
|
+
RInitial | StoreMaker | RepositoryRegistry
|
|
108
109
|
>
|
|
109
110
|
} = <
|
|
110
111
|
ItemType extends string,
|
|
@@ -135,5 +136,7 @@ export const makeRepo: {
|
|
|
135
136
|
let r = yield* mkRepo.make<RInitial, E, RPublish, RCtx>(options as any)
|
|
136
137
|
if (options.overrides) r = options.overrides(r)
|
|
137
138
|
const repo = extendRepo(r)
|
|
139
|
+
const registry = yield* RepositoryRegistry
|
|
140
|
+
registry.register(itemType, repo)
|
|
138
141
|
return repo
|
|
139
142
|
})
|