effect-app 4.0.0-beta.304 → 4.0.0-beta.306
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 +102 -73
- package/dist/DataDependencies.d.ts.map +1 -1
- package/dist/DataDependencies.js +13 -8
- package/dist/Model/Repository/internal/internal.d.ts +3 -0
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
- package/dist/Model/Repository/internal/internal.js +22 -10
- package/dist/Model/Repository/makeRepo.d.ts +7 -0
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +1 -1
- package/dist/Model/query/dsl.d.ts +89 -9
- package/dist/Model/query/dsl.d.ts.map +1 -1
- package/dist/Model/query/dsl.js +1 -1
- package/dist/rpc/Invalidation.d.ts +793 -577
- package/dist/rpc/Invalidation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/DataDependencies.ts +13 -5
- package/src/Model/Repository/internal/internal.ts +41 -9
- package/src/Model/Repository/makeRepo.ts +8 -0
- package/src/Model/query/dsl.ts +118 -14
|
@@ -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
|
@@ -14,7 +14,8 @@ export const DataDependency = S.Union([
|
|
|
14
14
|
}),
|
|
15
15
|
S.Struct({
|
|
16
16
|
type: S.Literal("signal"),
|
|
17
|
-
name: S.String
|
|
17
|
+
name: S.String,
|
|
18
|
+
ids: S.optional(S.NonEmptyArray(S.String))
|
|
18
19
|
})
|
|
19
20
|
])
|
|
20
21
|
export type DataDependency = S.Schema.Type<typeof DataDependency>
|
|
@@ -42,7 +43,6 @@ const sameDomain = (a: DataDependency, b: DataDependency) => a.type === b.type &
|
|
|
42
43
|
|
|
43
44
|
const dependenciesOverlap = (a: DataDependency, b: DataDependency) => {
|
|
44
45
|
if (!sameDomain(a, b)) return false
|
|
45
|
-
if (a.type === "signal" || b.type === "signal") return true
|
|
46
46
|
if (a.ids === undefined || b.ids === undefined) return true
|
|
47
47
|
return a.ids.some((id) => b.ids?.includes(id))
|
|
48
48
|
}
|
|
@@ -57,7 +57,6 @@ const containsDependency = (dependencies: ReadonlySet<DataDependency>, dependenc
|
|
|
57
57
|
const appendDependency = (dependency: DataDependency) => (dependencies: DataDependencies): DataDependencies => {
|
|
58
58
|
const existing = [...dependencies].find((_) => sameDomain(_, dependency))
|
|
59
59
|
if (existing === undefined) return new Set([...dependencies, dependency])
|
|
60
|
-
if (existing.type === "signal" || dependency.type === "signal") return dependencies
|
|
61
60
|
if (existing.ids === undefined) return dependencies
|
|
62
61
|
if (dependency.ids === undefined) {
|
|
63
62
|
return new Set([...dependencies].filter((_) => _ !== existing).concat(dependency))
|
|
@@ -68,10 +67,18 @@ const appendDependency = (dependency: DataDependency) => (dependencies: DataDepe
|
|
|
68
67
|
...new Set([...existing.ids.slice(1), ...dependency.ids].filter((id) => id !== first))
|
|
69
68
|
]
|
|
70
69
|
if (ids.length === existing.ids.length) return dependencies
|
|
71
|
-
const merged = repo(existing.name, ids)
|
|
70
|
+
const merged = existing.type === "repo" ? repo(existing.name, ids) : signal(existing.name, ids)
|
|
72
71
|
return new Set([...dependencies].filter((_) => _ !== existing).concat(merged))
|
|
73
72
|
}
|
|
74
73
|
|
|
74
|
+
export const merge = (...groups: ReadonlyArray<DataDependencies>): DataDependencies => {
|
|
75
|
+
let merged = empty()
|
|
76
|
+
for (const group of groups) {
|
|
77
|
+
for (const dependency of group) merged = appendDependency(dependency)(merged)
|
|
78
|
+
}
|
|
79
|
+
return merged
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
export const DataDependencyRecorder = RequestScopedDependencies.make(
|
|
76
83
|
"effect-app/DataDependencyRecorder",
|
|
77
84
|
Effect.gen(function*() {
|
|
@@ -125,7 +132,8 @@ export const makeDataDependencyRecorder = (
|
|
|
125
132
|
|
|
126
133
|
export const repo = (name: string, ids?: NonEmptyReadonlyArray<string>): DataDependency =>
|
|
127
134
|
ids === undefined ? { type: "repo", name } : { type: "repo", name, ids }
|
|
128
|
-
export const signal = (name: string): DataDependency =>
|
|
135
|
+
export const signal = (name: string, ids?: NonEmptyReadonlyArray<string>): DataDependency =>
|
|
136
|
+
ids === undefined ? { type: "signal", name } : { type: "signal", name, ids }
|
|
129
137
|
|
|
130
138
|
const RepoReadScope = Context.Reference<DataDependency | undefined>("effect-app/DataDependencies/RepoReadScope", {
|
|
131
139
|
defaultValue: () => undefined
|
|
@@ -115,6 +115,7 @@ export function makeRepoInternal<
|
|
|
115
115
|
schemaContext?: Context.Context<RCtx>
|
|
116
116
|
makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
|
|
117
117
|
dependencyIds?: (item: T) => NonEmptyReadonlyArray<string>
|
|
118
|
+
additionalWriteDependencies?: (item: T) => ReadonlyArray<DataDependencies.DataDependency>
|
|
118
119
|
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
119
120
|
partitionValue?: (e?: Encoded) => string
|
|
120
121
|
}
|
|
@@ -124,6 +125,7 @@ export function makeRepoInternal<
|
|
|
124
125
|
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
|
|
125
126
|
makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
|
|
126
127
|
dependencyIds?: (item: T) => NonEmptyReadonlyArray<string>
|
|
128
|
+
additionalWriteDependencies?: (item: T) => ReadonlyArray<DataDependencies.DataDependency>
|
|
127
129
|
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
128
130
|
partitionValue?: (e?: Encoded) => string
|
|
129
131
|
}
|
|
@@ -148,18 +150,28 @@ export function makeRepoInternal<
|
|
|
148
150
|
const recordRead = DataDependencies.readRepo(name)
|
|
149
151
|
const entityDependency = (ids: NonEmptyReadonlyArray<T[IdKey]>) =>
|
|
150
152
|
DataDependencies.repo(name, [String(ids[0]), ...ids.slice(1).map(String)])
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
...
|
|
156
|
-
|
|
157
|
-
])
|
|
153
|
+
const itemDependencies = (item: T) => {
|
|
154
|
+
const ids = args.dependencyIds?.(item) ?? [String(item[idKey])]
|
|
155
|
+
return [
|
|
156
|
+
DataDependencies.repo(name, ids),
|
|
157
|
+
...(args.additionalWriteDependencies?.(item) ?? [])
|
|
158
|
+
]
|
|
158
159
|
}
|
|
159
160
|
const recordEntityRead = (id: T[IdKey]) => DataDependencies.read(entityDependency([id]))
|
|
160
161
|
const recordEntityWrite = (ids: NonEmptyReadonlyArray<T[IdKey]>) =>
|
|
161
162
|
DataDependencies.write(entityDependency(ids))
|
|
162
|
-
const recordItemWrite = (items: NonEmptyReadonlyArray<T>) =>
|
|
163
|
+
const recordItemWrite = (items: NonEmptyReadonlyArray<T>) =>
|
|
164
|
+
Effect.forEach(
|
|
165
|
+
DataDependencies.merge(new Set(items.flatMap(itemDependencies))),
|
|
166
|
+
DataDependencies.write,
|
|
167
|
+
{ discard: true }
|
|
168
|
+
)
|
|
169
|
+
const recordAdditionalItemWrites = (items: ReadonlyArray<T>) =>
|
|
170
|
+
Effect.forEach(
|
|
171
|
+
DataDependencies.merge(new Set(items.flatMap((item) => args.additionalWriteDependencies?.(item) ?? []))),
|
|
172
|
+
DataDependencies.write,
|
|
173
|
+
{ discard: true }
|
|
174
|
+
)
|
|
163
175
|
const cms = Effect.map(getContextMap.pipe(Effect.orDie), (_) => ({
|
|
164
176
|
get: (id: string) => _.get(`${name}.${id}`),
|
|
165
177
|
set: (id: string, etag: string | undefined) => _.set(`${name}.${id}`, etag)
|
|
@@ -292,6 +304,20 @@ export function makeRepoInternal<
|
|
|
292
304
|
)
|
|
293
305
|
})
|
|
294
306
|
|
|
307
|
+
const loadExistingItems = (ids: ReadonlyArray<T[IdKey]>) =>
|
|
308
|
+
Effect
|
|
309
|
+
.forEach(
|
|
310
|
+
ids,
|
|
311
|
+
(id) =>
|
|
312
|
+
findE(id).pipe(
|
|
313
|
+
Effect.flatMap(Option.match({
|
|
314
|
+
onNone: () => Effect.succeed(Option.none<T>()),
|
|
315
|
+
onSome: (encoded) => decode(encoded).pipe(Effect.orDie, Effect.map(Option.some))
|
|
316
|
+
}))
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
.pipe(Effect.map(Array.getSomes))
|
|
320
|
+
|
|
295
321
|
const find = Effect.fn("Repository.find", {
|
|
296
322
|
kind: "client",
|
|
297
323
|
attributes: { "app.entity": name }
|
|
@@ -327,7 +353,10 @@ export function makeRepoInternal<
|
|
|
327
353
|
const it = Chunk.fromIterable(items)
|
|
328
354
|
if (Chunk.isNonEmpty(it)) {
|
|
329
355
|
const values = Chunk.toReadonlyArray(it)
|
|
330
|
-
|
|
356
|
+
const previous = args.additionalWriteDependencies
|
|
357
|
+
? yield* loadExistingItems(values.map((item) => item[idKey]))
|
|
358
|
+
: []
|
|
359
|
+
yield* recordItemWrite([values[0], ...values.slice(1), ...previous])
|
|
331
360
|
}
|
|
332
361
|
const evts = [...events]
|
|
333
362
|
yield* Effect.annotateCurrentSpan({
|
|
@@ -385,6 +414,9 @@ export function makeRepoInternal<
|
|
|
385
414
|
return
|
|
386
415
|
}
|
|
387
416
|
yield* recordEntityWrite(ids)
|
|
417
|
+
if (args.additionalWriteDependencies) {
|
|
418
|
+
yield* loadExistingItems(ids).pipe(Effect.flatMap(recordAdditionalItemWrites))
|
|
419
|
+
}
|
|
388
420
|
const { set } = yield* cms
|
|
389
421
|
const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
|
|
390
422
|
yield* Effect.annotateCurrentSpan({ "app.entity.ids": eids })
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type * as Scope from "effect/Scope"
|
|
10
10
|
import type { NonEmptyReadonlyArray } from "../../Array.ts"
|
|
11
11
|
import type * as Context from "../../Context.ts"
|
|
12
|
+
import type * as DataDependencies from "../../DataDependencies.ts"
|
|
12
13
|
import * as Effect from "../../Effect.ts"
|
|
13
14
|
import type * as S from "../../Schema.ts"
|
|
14
15
|
import type { StoreConfig, StoreMaker } from "../../Store.ts"
|
|
@@ -61,6 +62,13 @@ export interface RepositoryOptions<
|
|
|
61
62
|
/** IDs, including aliases, that identify an entity for dependency invalidation. */
|
|
62
63
|
dependencyIds?: (item: T) => NonEmptyReadonlyArray<string>
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Additional derived queries or resources affected by writing an item.
|
|
67
|
+
* Saves invalidate dependencies derived from both the previous and next item;
|
|
68
|
+
* removals derive them from the removed item.
|
|
69
|
+
*/
|
|
70
|
+
additionalWriteDependencies?: (item: T) => ReadonlyArray<DataDependencies.DataDependency>
|
|
71
|
+
|
|
64
72
|
overrides?: (
|
|
65
73
|
repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
|
|
66
74
|
) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
|
package/src/Model/query/dsl.ts
CHANGED
|
@@ -62,19 +62,96 @@ type LiteralValue<T> = T extends { readonly literal: infer L } ? L : T
|
|
|
62
62
|
type ExtractTagged<From, Tag> = From extends { readonly _tag: infer FromTag }
|
|
63
63
|
? [LiteralValue<FromTag>] extends [LiteralValue<Tag>] ? From : never
|
|
64
64
|
: never
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Domain shape that may supply stored fields for projection member `I`.
|
|
67
|
+
*
|
|
68
|
+
* - True tagged unions (`A | B` with different fields per `_tag`) resolve to the
|
|
69
|
+
* matching member so state-owned keys are not treated as universal.
|
|
70
|
+
* - Flat models that only carry `_tag: "a" | "b"` on a shared shape (no
|
|
71
|
+
* per-tag members in the Encoded union) fall back to the full `From` so
|
|
72
|
+
* existing Class+Literals projections keep typechecking.
|
|
73
|
+
*/
|
|
74
|
+
type ProjectableSource<I, From> = I extends { readonly _tag: infer Tag } ? (
|
|
75
|
+
[ExtractTagged<From, Tag>] extends [never] ? From : ExtractTagged<From, Tag>
|
|
76
|
+
)
|
|
66
77
|
: From
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* One projection member is projectable when every key is either:
|
|
81
|
+
* - in `ExtraKeys` (computed by the query / not stored on the domain row), or
|
|
82
|
+
* - a key of the matching domain source member (key presence only; nested
|
|
83
|
+
* field types may be narrowed by the projection).
|
|
84
|
+
*
|
|
85
|
+
* Uses `keyof Source` (not `KeysOfUnion` of the whole domain union) so a field
|
|
86
|
+
* owned only by some tags cannot be required on every branch.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Keys the domain may supply for projection member `I`.
|
|
90
|
+
* - Tagged `I`: only keys of the matching domain state.
|
|
91
|
+
* - Untagged `I` (plain project DTOs): keys present on *any* domain member
|
|
92
|
+
* (`KeysOfUnion`), matching historical `project()` behavior.
|
|
93
|
+
*/
|
|
94
|
+
type ProjectableDomainKeys<I, From> = I extends { readonly _tag: any } ? keyof ProjectableSource<I, From>
|
|
95
|
+
: KeysOfUnion<From>
|
|
96
|
+
|
|
97
|
+
type ProjectableEncodedMember<
|
|
98
|
+
I,
|
|
99
|
+
From,
|
|
100
|
+
ExtraKeys extends PropertyKey = never
|
|
101
|
+
> = I extends FieldValues ? {
|
|
102
|
+
// Keep `I[K]` (key presence only). Requiring domain field types would reject
|
|
103
|
+
// legitimate projections that narrow nested shapes (e.g. package views).
|
|
104
|
+
[K in keyof I]-?: K extends ExtraKeys ? I[K]
|
|
105
|
+
: K extends ProjectableDomainKeys<I, From> ? I[K]
|
|
106
|
+
: never
|
|
75
107
|
}
|
|
76
108
|
: never
|
|
77
|
-
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Distribute over tagged-union projection Encoded types. A non-distributive
|
|
112
|
+
* `[I] extends [...]` check against a union only sees `keyof (A|B)` (key
|
|
113
|
+
* intersection) and misses branch-only fields like cancel-only omissions.
|
|
114
|
+
*/
|
|
115
|
+
type IsProjectableMember<I, From, ExtraKeys extends PropertyKey> = [I] extends
|
|
116
|
+
[ProjectableEncodedMember<I, From, ExtraKeys>] ? true : false
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* `unknown` when every member of projection Encoded `I` is projectable from
|
|
120
|
+
* domain Encoded `From` (plus optional ExtraKeys for computed fields); `never`
|
|
121
|
+
* otherwise — use as an intersection constraint on a schema argument.
|
|
122
|
+
*/
|
|
123
|
+
type ProjectableGuard<I, From, ExtraKeys extends PropertyKey = never> = false extends (
|
|
124
|
+
I extends any ? IsProjectableMember<I, From, ExtraKeys> : never
|
|
125
|
+
) ? never
|
|
126
|
+
: unknown
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Compile-time proof that a projection Encoded shape only requires stored keys
|
|
130
|
+
* that exist on the matching domain tagged state (or non-tagged source), plus
|
|
131
|
+
* any `ExtraKeys` filled by `projectComputed` (counts, flags, collects, …).
|
|
132
|
+
*
|
|
133
|
+
* Catches the class of Overview.List SchemaError where a cancel/recovery state
|
|
134
|
+
* omits a workflow lock field (`activeRequest`) in the domain model but the
|
|
135
|
+
* projection still requires it on every branch.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* type _ok = ProjectableFromDomain<
|
|
140
|
+
* { readonly _tag: "cancelled"; readonly id: string },
|
|
141
|
+
* DomainEnc
|
|
142
|
+
* > // unknown
|
|
143
|
+
*
|
|
144
|
+
* type _bad = ProjectableFromDomain<
|
|
145
|
+
* { readonly _tag: "cancelled"; readonly id: string; readonly activeRequest: null },
|
|
146
|
+
* DomainEnc
|
|
147
|
+
* > // never — activeRequest is not on domain cancelled
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export type ProjectableFromDomain<
|
|
151
|
+
ProjectionEncoded,
|
|
152
|
+
DomainEncoded,
|
|
153
|
+
ExtraKeys extends PropertyKey = never
|
|
154
|
+
> = ProjectableGuard<ProjectionEncoded, DomainEncoded, ExtraKeys>
|
|
78
155
|
|
|
79
156
|
export type RelationDirection = "some" | "every"
|
|
80
157
|
export type Relation = { relation: RelationDirection }
|
|
@@ -839,6 +916,33 @@ const makeComputedHelpers = <TFieldValues extends FieldValues>(): ComputedHelper
|
|
|
839
916
|
relation: (path) => relation<TFieldValues, typeof path>(path)
|
|
840
917
|
})
|
|
841
918
|
|
|
919
|
+
/**
|
|
920
|
+
* Only treat computed-map keys as ExtraKeys when `M` is a concrete object type.
|
|
921
|
+
* `ComputedProjectionMap` is `Record<string, …>`, so `keyof M` is `string` and
|
|
922
|
+
* would otherwise allow every projection field as "computed".
|
|
923
|
+
*/
|
|
924
|
+
type ConcreteComputedKeys<M> = string extends keyof M ? never : Extract<keyof M, PropertyKey>
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Proof that projection Encoded `I` is projectable from domain Encoded, allowing
|
|
928
|
+
* concrete computed keys. Intersected onto the computed-map argument so
|
|
929
|
+
* inference of `M` is complete before the check runs.
|
|
930
|
+
*/
|
|
931
|
+
type ProjectableComputedMap<
|
|
932
|
+
M extends ComputedProjectionMap,
|
|
933
|
+
I extends FieldValues,
|
|
934
|
+
Domain
|
|
935
|
+
> =
|
|
936
|
+
& M
|
|
937
|
+
& NoExtraComputedKeys<M, I>
|
|
938
|
+
& (
|
|
939
|
+
[ProjectableGuard<I, Domain, ConcreteComputedKeys<M>>] extends [never] ? {
|
|
940
|
+
readonly __projectableFromDomain:
|
|
941
|
+
"projection fields must exist on the matching domain tagged state or be computed keys"
|
|
942
|
+
}
|
|
943
|
+
: unknown
|
|
944
|
+
)
|
|
945
|
+
|
|
842
946
|
export const projectComputed: {
|
|
843
947
|
<
|
|
844
948
|
Q extends Query<any> | QueryWhere<any, any, any> | QueryEnd<any, "one" | "many", any>,
|
|
@@ -848,7 +952,7 @@ export const projectComputed: {
|
|
|
848
952
|
E extends boolean = ExtractExclusiveness<Q>
|
|
849
953
|
>(
|
|
850
954
|
schema: Schema,
|
|
851
|
-
build: (helpers: ComputedHelpers<ExtractFieldValues<Q>>) =>
|
|
955
|
+
build: (helpers: ComputedHelpers<ExtractFieldValues<Q>>) => ProjectableComputedMap<M, I, ExtractFieldValues<Q>>,
|
|
852
956
|
mode: "collect"
|
|
853
957
|
): (
|
|
854
958
|
current: Q
|
|
@@ -868,7 +972,7 @@ export const projectComputed: {
|
|
|
868
972
|
E extends boolean = ExtractExclusiveness<Q>
|
|
869
973
|
>(
|
|
870
974
|
schema: Schema,
|
|
871
|
-
build: (helpers: ComputedHelpers<ExtractFieldValues<Q>>) =>
|
|
975
|
+
build: (helpers: ComputedHelpers<ExtractFieldValues<Q>>) => ProjectableComputedMap<M, I, ExtractFieldValues<Q>>,
|
|
872
976
|
mode?: "project"
|
|
873
977
|
): (
|
|
874
978
|
current: Q
|
|
@@ -882,7 +986,7 @@ export const projectComputed: {
|
|
|
882
986
|
E extends boolean = ExtractExclusiveness<Q>
|
|
883
987
|
>(
|
|
884
988
|
schema: Schema,
|
|
885
|
-
computedProjection:
|
|
989
|
+
computedProjection: ProjectableComputedMap<M, I, ExtractFieldValues<Q>>,
|
|
886
990
|
mode: "collect"
|
|
887
991
|
): (
|
|
888
992
|
current: Q
|
|
@@ -902,7 +1006,7 @@ export const projectComputed: {
|
|
|
902
1006
|
E extends boolean = ExtractExclusiveness<Q>
|
|
903
1007
|
>(
|
|
904
1008
|
schema: Schema,
|
|
905
|
-
computedProjection:
|
|
1009
|
+
computedProjection: ProjectableComputedMap<M, I, ExtractFieldValues<Q>>,
|
|
906
1010
|
mode?: "project"
|
|
907
1011
|
): (
|
|
908
1012
|
current: Q
|