effect-app 4.0.0-beta.302 → 4.0.0-beta.304
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 +12 -0
- package/dist/DataDependencies.d.ts +173 -310
- package/dist/DataDependencies.d.ts.map +1 -1
- package/dist/DataDependencies.js +51 -8
- package/dist/Model/Repository/internal/internal.d.ts +2 -0
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +67 -32
- package/dist/Model/Repository/makeRepo.d.ts +2 -0
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +1 -1
- package/dist/rpc/Invalidation.d.ts +1506 -2586
- package/dist/rpc/Invalidation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/DataDependencies.ts +57 -8
- package/src/Model/Repository/internal/internal.ts +129 -51
- package/src/Model/Repository/makeRepo.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Invalidation.d.ts","sourceRoot":"","sources":["../../src/rpc/Invalidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AACzC,OAAO,EAAE,KAAK,gBAAgB,EAAgB,MAAM,wBAAwB,CAAA;AAC5E,OAAO,KAAK,OAAO,MAAM,eAAe,CAAA;AACxC,OAAO,KAAK,gBAAgB,MAAM,wBAAwB,CAAA;AAC1D,OAAO,KAAK,MAAM,MAAM,cAAc,CAAA;AACtC,OAAO,KAAK,CAAC,MAAM,cAAc,CAAA;AAEjC;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAmBjH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,uCAAS,CAAA;AAC5C,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAEjF,6GAA6G;AAC7G,eAAO,MAAM,eAAe;;;CAAkC,CAAA;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAA;AAEnE,oFAAoF;AACpF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;CAA2B,CAAA;AACxD,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAErE,wFAAwF;AACxF,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"Invalidation.d.ts","sourceRoot":"","sources":["../../src/rpc/Invalidation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/DataDependencies.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import type { NonEmptyReadonlyArray } from "effect/Array"
|
|
1
2
|
import * as Ref from "effect/Ref"
|
|
2
3
|
import * as Stream from "effect/Stream"
|
|
4
|
+
import * as Context from "./Context.ts"
|
|
3
5
|
import * as Effect from "./Effect.ts"
|
|
4
6
|
import * as RequestScopedDependencies from "./RequestScopedDependencies.ts"
|
|
5
7
|
import * as S from "./Schema.ts"
|
|
6
8
|
|
|
7
|
-
export const DataDependency = S.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export const DataDependency = S.Union([
|
|
10
|
+
S.Struct({
|
|
11
|
+
type: S.Literal("repo"),
|
|
12
|
+
name: S.String,
|
|
13
|
+
ids: S.optional(S.NonEmptyArray(S.String))
|
|
14
|
+
}),
|
|
15
|
+
S.Struct({
|
|
16
|
+
type: S.Literal("signal"),
|
|
17
|
+
name: S.String
|
|
18
|
+
})
|
|
19
|
+
])
|
|
11
20
|
export type DataDependency = S.Schema.Type<typeof DataDependency>
|
|
12
21
|
|
|
13
22
|
export const DataDependencies = S.ReadonlySet(DataDependency)
|
|
@@ -29,15 +38,39 @@ export interface DataDependencyRecorderService {
|
|
|
29
38
|
readonly drainWrites: Effect.Effect<DataDependencies>
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
const sameDomain = (a: DataDependency, b: DataDependency) => a.type === b.type && a.name === b.name
|
|
42
|
+
|
|
43
|
+
const dependenciesOverlap = (a: DataDependency, b: DataDependency) => {
|
|
44
|
+
if (!sameDomain(a, b)) return false
|
|
45
|
+
if (a.type === "signal" || b.type === "signal") return true
|
|
46
|
+
if (a.ids === undefined || b.ids === undefined) return true
|
|
47
|
+
return a.ids.some((id) => b.ids?.includes(id))
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
const containsDependency = (dependencies: ReadonlySet<DataDependency>, dependency: DataDependency) => {
|
|
33
51
|
for (const _ of dependencies) {
|
|
34
|
-
if (_
|
|
52
|
+
if (dependenciesOverlap(_, dependency)) return true
|
|
35
53
|
}
|
|
36
54
|
return false
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
const appendDependency = (dependency: DataDependency) => (dependencies: DataDependencies): DataDependencies =>
|
|
40
|
-
|
|
57
|
+
const appendDependency = (dependency: DataDependency) => (dependencies: DataDependencies): DataDependencies => {
|
|
58
|
+
const existing = [...dependencies].find((_) => sameDomain(_, dependency))
|
|
59
|
+
if (existing === undefined) return new Set([...dependencies, dependency])
|
|
60
|
+
if (existing.type === "signal" || dependency.type === "signal") return dependencies
|
|
61
|
+
if (existing.ids === undefined) return dependencies
|
|
62
|
+
if (dependency.ids === undefined) {
|
|
63
|
+
return new Set([...dependencies].filter((_) => _ !== existing).concat(dependency))
|
|
64
|
+
}
|
|
65
|
+
const first = existing.ids[0]
|
|
66
|
+
const ids: NonEmptyReadonlyArray<string> = [
|
|
67
|
+
first,
|
|
68
|
+
...new Set([...existing.ids.slice(1), ...dependency.ids].filter((id) => id !== first))
|
|
69
|
+
]
|
|
70
|
+
if (ids.length === existing.ids.length) return dependencies
|
|
71
|
+
const merged = repo(existing.name, ids)
|
|
72
|
+
return new Set([...dependencies].filter((_) => _ !== existing).concat(merged))
|
|
73
|
+
}
|
|
41
74
|
|
|
42
75
|
export const DataDependencyRecorder = RequestScopedDependencies.make(
|
|
43
76
|
"effect-app/DataDependencyRecorder",
|
|
@@ -90,9 +123,25 @@ export const makeDataDependencyRecorder = (
|
|
|
90
123
|
drainWrites: Ref.getAndSet(writesRef, empty())
|
|
91
124
|
})
|
|
92
125
|
|
|
93
|
-
export const repo = (name: string): DataDependency =>
|
|
126
|
+
export const repo = (name: string, ids?: NonEmptyReadonlyArray<string>): DataDependency =>
|
|
127
|
+
ids === undefined ? { type: "repo", name } : { type: "repo", name, ids }
|
|
94
128
|
export const signal = (name: string): DataDependency => ({ type: "signal", name })
|
|
95
129
|
|
|
130
|
+
const RepoReadScope = Context.Reference<DataDependency | undefined>("effect-app/DataDependencies/RepoReadScope", {
|
|
131
|
+
defaultValue: () => undefined
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
export const readRepo = (name: string) =>
|
|
135
|
+
Effect.flatMap(
|
|
136
|
+
RepoReadScope,
|
|
137
|
+
(scope) => read(scope?.type === "repo" && scope.name === name ? scope : repo(name))
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
export const withRepoReadScope =
|
|
141
|
+
(name: string, ids: NonEmptyReadonlyArray<string>) =>
|
|
142
|
+
<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
143
|
+
Effect.provideService(self, RepoReadScope, repo(name, ids))
|
|
144
|
+
|
|
96
145
|
export const QueryReadDependenciesMetaKey = "effect-app.query.readDependencies"
|
|
97
146
|
|
|
98
147
|
export const read = (dependency: DataDependency) =>
|
|
@@ -4,6 +4,7 @@ import * as Equivalence from "effect/Equivalence"
|
|
|
4
4
|
import { flow, pipe } from "effect/Function"
|
|
5
5
|
import * as HashMap from "effect/HashMap"
|
|
6
6
|
import * as HashSet from "effect/HashSet"
|
|
7
|
+
import * as Metric from "effect/Metric"
|
|
7
8
|
import * as Pipeable from "effect/Pipeable"
|
|
8
9
|
import * as Ref from "effect/Ref"
|
|
9
10
|
import * as Result from "effect/Result"
|
|
@@ -31,6 +32,47 @@ import { ValidationError, ValidationResult } from "../validation.ts"
|
|
|
31
32
|
|
|
32
33
|
const dedupe = Array.dedupeWith(Equivalence.String)
|
|
33
34
|
|
|
35
|
+
const schemaDurationBoundaries = [0.1, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1_000]
|
|
36
|
+
const schemaDecodeDuration = Metric.histogram("app.schema.decode.duration", { boundaries: schemaDurationBoundaries })
|
|
37
|
+
const schemaEncodeDuration = Metric.histogram("app.schema.encode.duration", { boundaries: schemaDurationBoundaries })
|
|
38
|
+
const schemaItemCount = Metric.histogram("app.schema.item_count", {
|
|
39
|
+
boundaries: [0, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1_000, 5_000]
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const timeSchema = (
|
|
43
|
+
operation: "decode" | "encode",
|
|
44
|
+
entity: string,
|
|
45
|
+
queryMode: "aggregate" | "collect" | "project" | "transform" | undefined,
|
|
46
|
+
itemCount: number
|
|
47
|
+
) =>
|
|
48
|
+
<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
49
|
+
Effect.clockWith((clock) => {
|
|
50
|
+
const startedAt = clock.currentTimeNanosUnsafe()
|
|
51
|
+
const attributes = {
|
|
52
|
+
"app.entity": entity,
|
|
53
|
+
"app.schema.operation": operation,
|
|
54
|
+
...(queryMode !== undefined && { "app.query.mode": queryMode })
|
|
55
|
+
}
|
|
56
|
+
return Effect.onExit(self, () => {
|
|
57
|
+
const durationMs = Number(clock.currentTimeNanosUnsafe() - startedAt) / 1_000_000
|
|
58
|
+
return Effect.all([
|
|
59
|
+
Effect.annotateCurrentSpan({
|
|
60
|
+
[`app.schema.${operation}.duration_ms`]: durationMs,
|
|
61
|
+
"app.schema.item_count": itemCount,
|
|
62
|
+
...(queryMode !== undefined && { "app.query.mode": queryMode })
|
|
63
|
+
}),
|
|
64
|
+
Metric.update(
|
|
65
|
+
Metric.withAttributes(
|
|
66
|
+
operation === "decode" ? schemaDecodeDuration : schemaEncodeDuration,
|
|
67
|
+
attributes
|
|
68
|
+
),
|
|
69
|
+
durationMs
|
|
70
|
+
),
|
|
71
|
+
Metric.update(Metric.withAttributes(schemaItemCount, attributes), itemCount)
|
|
72
|
+
], { discard: true })
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
34
76
|
/**
|
|
35
77
|
* A base implementation to create a repository.
|
|
36
78
|
*/
|
|
@@ -72,6 +114,7 @@ export function makeRepoInternal<
|
|
|
72
114
|
args: [Evt] extends [never] ? {
|
|
73
115
|
schemaContext?: Context.Context<RCtx>
|
|
74
116
|
makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
|
|
117
|
+
dependencyIds?: (item: T) => NonEmptyReadonlyArray<string>
|
|
75
118
|
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
76
119
|
partitionValue?: (e?: Encoded) => string
|
|
77
120
|
}
|
|
@@ -80,6 +123,7 @@ export function makeRepoInternal<
|
|
|
80
123
|
schemaContext?: Context.Context<RCtx>
|
|
81
124
|
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
|
|
82
125
|
makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
|
|
126
|
+
dependencyIds?: (item: T) => NonEmptyReadonlyArray<string>
|
|
83
127
|
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
84
128
|
partitionValue?: (e?: Encoded) => string
|
|
85
129
|
}
|
|
@@ -89,11 +133,11 @@ export function makeRepoInternal<
|
|
|
89
133
|
.gen(function*() {
|
|
90
134
|
const rctx: Context.Context<RCtx> = args.schemaContext ?? Context.empty() as any
|
|
91
135
|
const provideRctx = Effect.provide(rctx)
|
|
92
|
-
const encodeMany =
|
|
93
|
-
S.encodeEffect(S.Array(schema))
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
136
|
+
const encodeMany = (items: readonly T[]) =>
|
|
137
|
+
S.encodeEffect(S.Array(schema))(items).pipe(
|
|
138
|
+
provideRctx,
|
|
139
|
+
timeSchema("encode", name, undefined, items.length)
|
|
140
|
+
)
|
|
97
141
|
const decode = flow(S.decodeEffectConcurrently(schema), provideRctx)
|
|
98
142
|
const decodeMany = flow(
|
|
99
143
|
S.decodeEffectConcurrently(S.Array(schema)),
|
|
@@ -101,9 +145,21 @@ export function makeRepoInternal<
|
|
|
101
145
|
)
|
|
102
146
|
|
|
103
147
|
const store = yield* mkStore(args.makeInitial, args.config)
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
148
|
+
const recordRead = DataDependencies.readRepo(name)
|
|
149
|
+
const entityDependency = (ids: NonEmptyReadonlyArray<T[IdKey]>) =>
|
|
150
|
+
DataDependencies.repo(name, [String(ids[0]), ...ids.slice(1).map(String)])
|
|
151
|
+
const itemDependency = (items: NonEmptyReadonlyArray<T>) => {
|
|
152
|
+
const firstIds = args.dependencyIds?.(items[0]) ?? [String(items[0][idKey])]
|
|
153
|
+
return DataDependencies.repo(name, [
|
|
154
|
+
firstIds[0],
|
|
155
|
+
...firstIds.slice(1),
|
|
156
|
+
...items.slice(1).flatMap((item) => args.dependencyIds?.(item) ?? [String(item[idKey])])
|
|
157
|
+
])
|
|
158
|
+
}
|
|
159
|
+
const recordEntityRead = (id: T[IdKey]) => DataDependencies.read(entityDependency([id]))
|
|
160
|
+
const recordEntityWrite = (ids: NonEmptyReadonlyArray<T[IdKey]>) =>
|
|
161
|
+
DataDependencies.write(entityDependency(ids))
|
|
162
|
+
const recordItemWrite = (items: NonEmptyReadonlyArray<T>) => DataDependencies.write(itemDependency(items))
|
|
107
163
|
const cms = Effect.map(getContextMap.pipe(Effect.orDie), (_) => ({
|
|
108
164
|
get: (id: string) => _.get(`${name}.${id}`),
|
|
109
165
|
set: (id: string, etag: string | undefined) => _.set(`${name}.${id}`, etag)
|
|
@@ -163,7 +219,11 @@ export function makeRepoInternal<
|
|
|
163
219
|
const all = Effect
|
|
164
220
|
.flatMap(
|
|
165
221
|
allE,
|
|
166
|
-
(
|
|
222
|
+
(items) =>
|
|
223
|
+
decodeMany(items).pipe(
|
|
224
|
+
Effect.orDie,
|
|
225
|
+
timeSchema("decode", name, undefined, items.length)
|
|
226
|
+
)
|
|
167
227
|
)
|
|
168
228
|
.pipe(
|
|
169
229
|
Effect.tap(() => recordRead),
|
|
@@ -236,7 +296,7 @@ export function makeRepoInternal<
|
|
|
236
296
|
kind: "client",
|
|
237
297
|
attributes: { "app.entity": name }
|
|
238
298
|
})(function*(id: T[IdKey]) {
|
|
239
|
-
yield*
|
|
299
|
+
yield* recordEntityRead(id)
|
|
240
300
|
yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
|
|
241
301
|
return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
|
|
242
302
|
})
|
|
@@ -264,8 +324,11 @@ export function makeRepoInternal<
|
|
|
264
324
|
|
|
265
325
|
const saveAndPublish = Effect.fn("Repository.saveAndPublish", { attributes: { "app.entity": name } })(
|
|
266
326
|
function*(items: Iterable<T>, events: Iterable<Evt> = []) {
|
|
267
|
-
yield* recordWrite
|
|
268
327
|
const it = Chunk.fromIterable(items)
|
|
328
|
+
if (Chunk.isNonEmpty(it)) {
|
|
329
|
+
const values = Chunk.toReadonlyArray(it)
|
|
330
|
+
yield* recordItemWrite([values[0], ...values.slice(1)])
|
|
331
|
+
}
|
|
269
332
|
const evts = [...events]
|
|
270
333
|
yield* Effect.annotateCurrentSpan({
|
|
271
334
|
"app.entity.ids": Chunk.map(it, (_) => _[idKey]),
|
|
@@ -284,9 +347,11 @@ export function makeRepoInternal<
|
|
|
284
347
|
|
|
285
348
|
const removeAndPublish = Effect.fn("Repository.removeAndPublish", { attributes: { "app.entity": name } })(
|
|
286
349
|
function*(a: Iterable<T>, events: Iterable<Evt> = []) {
|
|
287
|
-
yield* recordWrite
|
|
288
350
|
const { set } = yield* cms
|
|
289
351
|
const it = [...a]
|
|
352
|
+
if (Array.isReadonlyArrayNonEmpty(it)) {
|
|
353
|
+
yield* recordItemWrite(it)
|
|
354
|
+
}
|
|
290
355
|
const evts = [...events]
|
|
291
356
|
yield* Effect.annotateCurrentSpan({
|
|
292
357
|
"app.entity.ids": it.map((_) => _[idKey]),
|
|
@@ -313,13 +378,13 @@ export function makeRepoInternal<
|
|
|
313
378
|
|
|
314
379
|
const removeById = Effect.fn("Repository.removeById", { attributes: { "app.entity": name } })(
|
|
315
380
|
function*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
|
|
316
|
-
yield* recordWrite
|
|
317
381
|
const ids = globalThis.Array.isArray(idOrIds)
|
|
318
382
|
? idOrIds as readonly T[IdKey][]
|
|
319
383
|
: [idOrIds as T[IdKey]]
|
|
320
384
|
if (!Array.isReadonlyArrayNonEmpty(ids)) {
|
|
321
385
|
return
|
|
322
386
|
}
|
|
387
|
+
yield* recordEntityWrite(ids)
|
|
323
388
|
const { set } = yield* cms
|
|
324
389
|
const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
|
|
325
390
|
yield* Effect.annotateCurrentSpan({ "app.entity.ids": eids })
|
|
@@ -331,12 +396,13 @@ export function makeRepoInternal<
|
|
|
331
396
|
}
|
|
332
397
|
)
|
|
333
398
|
|
|
334
|
-
const parseMany = Effect.
|
|
335
|
-
attributes: { "app.entity": name, "app.query.mode": "transform" }
|
|
336
|
-
})(
|
|
399
|
+
const parseMany = Effect.fnUntraced(
|
|
337
400
|
function*(items: readonly PM[]) {
|
|
338
401
|
const cm = yield* cms
|
|
339
|
-
return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(
|
|
402
|
+
return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(
|
|
403
|
+
Effect.orDie,
|
|
404
|
+
timeSchema("decode", name, "transform", items.length)
|
|
405
|
+
)
|
|
340
406
|
}
|
|
341
407
|
)
|
|
342
408
|
const decodeManyCache = new WeakMap<
|
|
@@ -351,12 +417,13 @@ export function makeRepoInternal<
|
|
|
351
417
|
}
|
|
352
418
|
return dec
|
|
353
419
|
}
|
|
354
|
-
const parseMany2 = Effect.
|
|
355
|
-
attributes: { "app.entity": name, "app.query.mode": "transform" }
|
|
356
|
-
})(
|
|
420
|
+
const parseMany2 = Effect.fnUntraced(
|
|
357
421
|
function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
|
|
358
422
|
const cm = yield* cms
|
|
359
|
-
return yield* getDecodeMany(schema)(items.map((_) => mapReverse(_, cm.set))).pipe(
|
|
423
|
+
return yield* getDecodeMany(schema)(items.map((_) => mapReverse(_, cm.set))).pipe(
|
|
424
|
+
Effect.orDie,
|
|
425
|
+
timeSchema("decode", name, "transform", items.length)
|
|
426
|
+
)
|
|
360
427
|
}
|
|
361
428
|
)
|
|
362
429
|
const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
|
|
@@ -399,13 +466,11 @@ export function makeRepoInternal<
|
|
|
399
466
|
// Decode raw aggregate rows directly — no PM reverse-mapping, no id/_etag needed.
|
|
400
467
|
.pipe(
|
|
401
468
|
Effect.andThen(
|
|
402
|
-
|
|
403
|
-
S.decodeEffectConcurrently(S.Array(a.schema ?? schema))
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
})
|
|
408
|
-
)
|
|
469
|
+
(items) =>
|
|
470
|
+
S.decodeEffectConcurrently(S.Array(a.schema ?? schema))(items).pipe(
|
|
471
|
+
provideRctx,
|
|
472
|
+
timeSchema("decode", name, "aggregate", items.length)
|
|
473
|
+
)
|
|
409
474
|
)
|
|
410
475
|
)
|
|
411
476
|
: a.mode === "project"
|
|
@@ -413,27 +478,24 @@ export function makeRepoInternal<
|
|
|
413
478
|
// TODO: mapFrom but need to support per field and dependencies
|
|
414
479
|
.pipe(
|
|
415
480
|
Effect.andThen(
|
|
416
|
-
|
|
417
|
-
S.decodeEffectConcurrently(S.Array(a.schema ?? schema))
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
})
|
|
422
|
-
)
|
|
481
|
+
(items) =>
|
|
482
|
+
S.decodeEffectConcurrently(S.Array(a.schema ?? schema))(items).pipe(
|
|
483
|
+
provideRctx,
|
|
484
|
+
timeSchema("decode", name, "project", items.length)
|
|
485
|
+
)
|
|
423
486
|
)
|
|
424
487
|
)
|
|
425
488
|
: a.mode === "collect"
|
|
426
489
|
? filter(a)
|
|
427
490
|
// TODO: mapFrom but need to support per field and dependencies
|
|
428
491
|
.pipe(
|
|
429
|
-
Effect.flatMap(
|
|
430
|
-
S.decodeEffectConcurrently(S.Array(a.schema))
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
))
|
|
492
|
+
Effect.flatMap((items) =>
|
|
493
|
+
S.decodeEffectConcurrently(S.Array(a.schema))(items).pipe(
|
|
494
|
+
Effect.map(Array.getSomes),
|
|
495
|
+
provideRctx,
|
|
496
|
+
timeSchema("decode", name, "collect", items.length)
|
|
497
|
+
)
|
|
498
|
+
)
|
|
437
499
|
)
|
|
438
500
|
: Effect.flatMap(
|
|
439
501
|
filter(a),
|
|
@@ -559,9 +621,12 @@ export function makeRepoInternal<
|
|
|
559
621
|
seedNamespace: (namespace: string) => store.seedNamespace(namespace),
|
|
560
622
|
validateSample,
|
|
561
623
|
queryRaw<A, Out, QR>(schema: S.Codec<A, Out, QR>, q: Q.RawQuery<Encoded, Out>) {
|
|
562
|
-
const dec = S.decodeEffectConcurrently(S.Array(schema))
|
|
563
624
|
return store.queryRaw(q).pipe(
|
|
564
|
-
Effect.flatMap(
|
|
625
|
+
Effect.flatMap((items) =>
|
|
626
|
+
S.decodeEffectConcurrently(S.Array(schema))(items).pipe(
|
|
627
|
+
timeSchema("decode", name, undefined, items.length)
|
|
628
|
+
)
|
|
629
|
+
),
|
|
565
630
|
Effect.tap(() => recordRead),
|
|
566
631
|
Effect.withSpan("Repository.queryRaw", {
|
|
567
632
|
kind: "client",
|
|
@@ -583,14 +648,18 @@ export function makeRepoInternal<
|
|
|
583
648
|
const spanAttrs = { kind: "client" as const, attributes: { "app.entity": name } }
|
|
584
649
|
return {
|
|
585
650
|
all: allE.pipe(
|
|
586
|
-
Effect.flatMap(
|
|
651
|
+
Effect.flatMap((items) =>
|
|
652
|
+
decMany(items).pipe(
|
|
653
|
+
timeSchema("decode", name, undefined, items.length)
|
|
654
|
+
)
|
|
655
|
+
),
|
|
587
656
|
Effect.tap(() => recordRead),
|
|
588
657
|
Effect.map((_) => _ as any[]),
|
|
589
658
|
Effect.withSpan("Repository.mapped.all", spanAttrs, { captureStackTrace: false })
|
|
590
659
|
),
|
|
591
660
|
find: (id: T[IdKey]) =>
|
|
592
661
|
flatMapOption(findE(id), dec).pipe(
|
|
593
|
-
Effect.tap(() =>
|
|
662
|
+
Effect.tap(() => recordEntityRead(id)),
|
|
594
663
|
Effect.withSpan("Repository.mapped.find", {
|
|
595
664
|
...spanAttrs,
|
|
596
665
|
attributes: { ...spanAttrs.attributes, "app.entity.id": id }
|
|
@@ -613,10 +682,19 @@ export function makeRepoInternal<
|
|
|
613
682
|
// )
|
|
614
683
|
// },
|
|
615
684
|
save: (...xes: any[]) =>
|
|
616
|
-
Effect
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
685
|
+
Effect
|
|
686
|
+
.flatMap(
|
|
687
|
+
encMany(xes).pipe(timeSchema("encode", name, undefined, xes.length)),
|
|
688
|
+
(_) => saveAllE(_)
|
|
689
|
+
)
|
|
690
|
+
.pipe(
|
|
691
|
+
Effect.tap(() =>
|
|
692
|
+
Array.isReadonlyArrayNonEmpty(xes)
|
|
693
|
+
? recordEntityWrite([xes[0][idKey], ...xes.slice(1).map((_) => _[idKey])])
|
|
694
|
+
: Effect.void
|
|
695
|
+
),
|
|
696
|
+
Effect.withSpan("Repository.mapped.save", spanAttrs, { captureStackTrace: false })
|
|
697
|
+
)
|
|
620
698
|
}
|
|
621
699
|
}
|
|
622
700
|
}
|
|
@@ -58,6 +58,9 @@ export interface RepositoryOptions<
|
|
|
58
58
|
*/
|
|
59
59
|
schemaContext?: Context.Context<RCtx>
|
|
60
60
|
|
|
61
|
+
/** IDs, including aliases, that identify an entity for dependency invalidation. */
|
|
62
|
+
dependencyIds?: (item: T) => NonEmptyReadonlyArray<string>
|
|
63
|
+
|
|
61
64
|
overrides?: (
|
|
62
65
|
repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
|
|
63
66
|
) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
|