effect 3.18.5 → 3.19.1
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/HashRing/package.json +6 -0
- package/dist/cjs/Array.js.map +1 -1
- package/dist/cjs/Effect.js.map +1 -1
- package/dist/cjs/Graph.js +286 -177
- package/dist/cjs/Graph.js.map +1 -1
- package/dist/cjs/HashRing.js +257 -0
- package/dist/cjs/HashRing.js.map +1 -0
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/layer.js +28 -8
- package/dist/cjs/internal/layer.js.map +1 -1
- package/dist/cjs/internal/opCodes/layer.js +3 -1
- package/dist/cjs/internal/opCodes/layer.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Array.d.ts +3 -3
- package/dist/dts/Array.d.ts.map +1 -1
- package/dist/dts/Effect.d.ts +5 -0
- package/dist/dts/Effect.d.ts.map +1 -1
- package/dist/dts/Graph.d.ts +147 -49
- package/dist/dts/Graph.d.ts.map +1 -1
- package/dist/dts/HashRing.d.ts +158 -0
- package/dist/dts/HashRing.d.ts.map +1 -0
- package/dist/dts/Types.d.ts +1 -1
- package/dist/dts/Types.d.ts.map +1 -1
- package/dist/dts/index.d.ts +5 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/layer.d.ts.map +1 -1
- package/dist/esm/Array.js.map +1 -1
- package/dist/esm/Effect.js.map +1 -1
- package/dist/esm/Graph.js +282 -175
- package/dist/esm/Graph.js.map +1 -1
- package/dist/esm/HashRing.js +245 -0
- package/dist/esm/HashRing.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/layer.js +28 -8
- package/dist/esm/internal/layer.js.map +1 -1
- package/dist/esm/internal/opCodes/layer.js +2 -0
- package/dist/esm/internal/opCodes/layer.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +9 -1
- package/src/Array.ts +4 -4
- package/src/Effect.ts +5 -0
- package/src/Graph.ts +410 -218
- package/src/HashRing.ts +387 -0
- package/src/Types.ts +3 -1
- package/src/index.ts +6 -0
- package/src/internal/layer.ts +50 -13
- package/src/internal/opCodes/layer.ts +6 -0
- package/src/internal/version.ts +1 -1
package/src/HashRing.ts
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 3.19.0
|
|
3
|
+
* @experimental
|
|
4
|
+
*/
|
|
5
|
+
import { dual } from "./Function.js"
|
|
6
|
+
import * as Hash from "./Hash.js"
|
|
7
|
+
import * as Inspectable from "./Inspectable.js"
|
|
8
|
+
import * as Iterable from "./Iterable.js"
|
|
9
|
+
import { type Pipeable, pipeArguments } from "./Pipeable.js"
|
|
10
|
+
import { hasProperty } from "./Predicate.js"
|
|
11
|
+
import * as PrimaryKey from "./PrimaryKey.js"
|
|
12
|
+
|
|
13
|
+
const TypeId = "~effect/cluster/HashRing" as const
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @since 3.19.0
|
|
17
|
+
* @category Models
|
|
18
|
+
* @experimental
|
|
19
|
+
*/
|
|
20
|
+
export interface HashRing<A extends PrimaryKey.PrimaryKey> extends Pipeable, Iterable<A> {
|
|
21
|
+
readonly [TypeId]: typeof TypeId
|
|
22
|
+
readonly baseWeight: number
|
|
23
|
+
totalWeightCache: number
|
|
24
|
+
readonly nodes: Map<string, [node: A, weight: number]>
|
|
25
|
+
ring: Array<[hash: number, node: string]>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @since 3.19.0
|
|
30
|
+
* @category Guards
|
|
31
|
+
* @experimental
|
|
32
|
+
*/
|
|
33
|
+
export const isHashRing = (u: unknown): u is HashRing<any> => hasProperty(u, TypeId)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @since 3.19.0
|
|
37
|
+
* @category Constructors
|
|
38
|
+
* @experimental
|
|
39
|
+
*/
|
|
40
|
+
export const make = <A extends PrimaryKey.PrimaryKey>(options?: {
|
|
41
|
+
readonly baseWeight?: number | undefined
|
|
42
|
+
}): HashRing<A> => {
|
|
43
|
+
const self = Object.create(Proto)
|
|
44
|
+
self.baseWeight = Math.max(options?.baseWeight ?? 128, 1)
|
|
45
|
+
self.totalWeightCache = 0
|
|
46
|
+
self.nodes = new Map()
|
|
47
|
+
self.ring = []
|
|
48
|
+
return self
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const Proto = {
|
|
52
|
+
[TypeId]: TypeId,
|
|
53
|
+
[Symbol.iterator]<A extends PrimaryKey.PrimaryKey>(this: HashRing<A>): Iterator<A> {
|
|
54
|
+
return Iterable.map(this.nodes.values(), ([n]) => n)[Symbol.iterator]()
|
|
55
|
+
},
|
|
56
|
+
pipe() {
|
|
57
|
+
return pipeArguments(this, arguments)
|
|
58
|
+
},
|
|
59
|
+
...Inspectable.BaseProto,
|
|
60
|
+
toJSON(this: HashRing<any>) {
|
|
61
|
+
return {
|
|
62
|
+
_id: "HashRing",
|
|
63
|
+
baseWeight: this.baseWeight,
|
|
64
|
+
nodes: this.ring.map(([, n]) => this.nodes.get(n)![0])
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add new nodes to the ring. If a node already exists in the ring, it
|
|
71
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
72
|
+
*
|
|
73
|
+
* @since 3.19.0
|
|
74
|
+
* @category Combinators
|
|
75
|
+
* @experimental
|
|
76
|
+
*/
|
|
77
|
+
export const addMany: {
|
|
78
|
+
/**
|
|
79
|
+
* Add new nodes to the ring. If a node already exists in the ring, it
|
|
80
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
81
|
+
*
|
|
82
|
+
* @since 3.19.0
|
|
83
|
+
* @category Combinators
|
|
84
|
+
* @experimental
|
|
85
|
+
*/
|
|
86
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
87
|
+
nodes: Iterable<A>,
|
|
88
|
+
options?: {
|
|
89
|
+
readonly weight?: number | undefined
|
|
90
|
+
}
|
|
91
|
+
): (self: HashRing<A>) => HashRing<A>
|
|
92
|
+
/**
|
|
93
|
+
* Add new nodes to the ring. If a node already exists in the ring, it
|
|
94
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
95
|
+
*
|
|
96
|
+
* @since 3.19.0
|
|
97
|
+
* @category Combinators
|
|
98
|
+
* @experimental
|
|
99
|
+
*/
|
|
100
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
101
|
+
self: HashRing<A>,
|
|
102
|
+
nodes: Iterable<A>,
|
|
103
|
+
options?: {
|
|
104
|
+
readonly weight?: number | undefined
|
|
105
|
+
}
|
|
106
|
+
): HashRing<A>
|
|
107
|
+
} = dual(
|
|
108
|
+
(args) => isHashRing(args[0]),
|
|
109
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, nodes: Iterable<A>, options?: {
|
|
110
|
+
readonly weight?: number | undefined
|
|
111
|
+
}): HashRing<A> => {
|
|
112
|
+
const weight = Math.max(options?.weight ?? 1, 0.1)
|
|
113
|
+
const keys: Array<string> = []
|
|
114
|
+
let toRemove: Set<string> | undefined
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
const key = PrimaryKey.value(node)
|
|
117
|
+
const entry = self.nodes.get(key)
|
|
118
|
+
if (entry) {
|
|
119
|
+
if (entry[1] === weight) continue
|
|
120
|
+
toRemove ??= new Set()
|
|
121
|
+
toRemove.add(key)
|
|
122
|
+
self.totalWeightCache -= entry[1]
|
|
123
|
+
self.totalWeightCache += weight
|
|
124
|
+
entry[1] = weight
|
|
125
|
+
} else {
|
|
126
|
+
self.nodes.set(key, [node, weight])
|
|
127
|
+
self.totalWeightCache += weight
|
|
128
|
+
}
|
|
129
|
+
keys.push(key)
|
|
130
|
+
}
|
|
131
|
+
if (toRemove) {
|
|
132
|
+
self.ring = self.ring.filter(([, n]) => !toRemove.has(n))
|
|
133
|
+
}
|
|
134
|
+
addNodesToRing(self, keys, Math.round(weight * self.baseWeight))
|
|
135
|
+
return self
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
function addNodesToRing<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, keys: Array<string>, weight: number) {
|
|
140
|
+
for (let i = weight; i > 0; i--) {
|
|
141
|
+
for (let j = 0; j < keys.length; j++) {
|
|
142
|
+
const key = keys[j]
|
|
143
|
+
self.ring.push([
|
|
144
|
+
Hash.string(`${key}:${i}`),
|
|
145
|
+
key
|
|
146
|
+
])
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
self.ring.sort((a, b) => a[0] - b[0])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Add a new node to the ring. If the node already exists in the ring, it
|
|
154
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
155
|
+
*
|
|
156
|
+
* @since 3.19.0
|
|
157
|
+
* @category Combinators
|
|
158
|
+
* @experimental
|
|
159
|
+
*/
|
|
160
|
+
export const add: {
|
|
161
|
+
/**
|
|
162
|
+
* Add a new node to the ring. If the node already exists in the ring, it
|
|
163
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
164
|
+
*
|
|
165
|
+
* @since 3.19.0
|
|
166
|
+
* @category Combinators
|
|
167
|
+
* @experimental
|
|
168
|
+
*/
|
|
169
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
170
|
+
node: A,
|
|
171
|
+
options?: {
|
|
172
|
+
readonly weight?: number | undefined
|
|
173
|
+
}
|
|
174
|
+
): (self: HashRing<A>) => HashRing<A>
|
|
175
|
+
/**
|
|
176
|
+
* Add a new node to the ring. If the node already exists in the ring, it
|
|
177
|
+
* will be updated. For example, you can use this to update the node's weight.
|
|
178
|
+
*
|
|
179
|
+
* @since 3.19.0
|
|
180
|
+
* @category Combinators
|
|
181
|
+
* @experimental
|
|
182
|
+
*/
|
|
183
|
+
<A extends PrimaryKey.PrimaryKey>(
|
|
184
|
+
self: HashRing<A>,
|
|
185
|
+
node: A,
|
|
186
|
+
options?: {
|
|
187
|
+
readonly weight?: number | undefined
|
|
188
|
+
}
|
|
189
|
+
): HashRing<A>
|
|
190
|
+
} = dual((args) => isHashRing(args[0]), <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A, options?: {
|
|
191
|
+
readonly weight?: number | undefined
|
|
192
|
+
}): HashRing<A> => addMany(self, [node], options))
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Removes the node from the ring. No-op's if the node does not exist.
|
|
196
|
+
*
|
|
197
|
+
* @since 3.19.0
|
|
198
|
+
* @category Combinators
|
|
199
|
+
* @experimental
|
|
200
|
+
*/
|
|
201
|
+
export const remove: {
|
|
202
|
+
/**
|
|
203
|
+
* Removes the node from the ring. No-op's if the node does not exist.
|
|
204
|
+
*
|
|
205
|
+
* @since 3.19.0
|
|
206
|
+
* @category Combinators
|
|
207
|
+
* @experimental
|
|
208
|
+
*/
|
|
209
|
+
<A extends PrimaryKey.PrimaryKey>(node: A): (self: HashRing<A>) => HashRing<A>
|
|
210
|
+
/**
|
|
211
|
+
* Removes the node from the ring. No-op's if the node does not exist.
|
|
212
|
+
*
|
|
213
|
+
* @since 3.19.0
|
|
214
|
+
* @category Combinators
|
|
215
|
+
* @experimental
|
|
216
|
+
*/
|
|
217
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): HashRing<A>
|
|
218
|
+
} = dual(2, <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): HashRing<A> => {
|
|
219
|
+
const key = PrimaryKey.value(node)
|
|
220
|
+
const entry = self.nodes.get(key)
|
|
221
|
+
if (entry) {
|
|
222
|
+
self.nodes.delete(key)
|
|
223
|
+
self.ring = self.ring.filter(([, n]) => n !== key)
|
|
224
|
+
self.totalWeightCache -= entry[1]
|
|
225
|
+
}
|
|
226
|
+
return self
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @since 3.19.0
|
|
231
|
+
* @category Combinators
|
|
232
|
+
* @experimental
|
|
233
|
+
*/
|
|
234
|
+
export const has: {
|
|
235
|
+
/**
|
|
236
|
+
* @since 3.19.0
|
|
237
|
+
* @category Combinators
|
|
238
|
+
* @experimental
|
|
239
|
+
*/
|
|
240
|
+
<A extends PrimaryKey.PrimaryKey>(node: A): (self: HashRing<A>) => boolean
|
|
241
|
+
/**
|
|
242
|
+
* @since 3.19.0
|
|
243
|
+
* @category Combinators
|
|
244
|
+
* @experimental
|
|
245
|
+
*/
|
|
246
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): boolean
|
|
247
|
+
} = dual(
|
|
248
|
+
2,
|
|
249
|
+
<A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, node: A): boolean => self.nodes.has(PrimaryKey.value(node))
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Gets the node which should handle the given input. Returns undefined if
|
|
254
|
+
* the hashring has no elements with weight.
|
|
255
|
+
*
|
|
256
|
+
* @since 3.19.0
|
|
257
|
+
* @category Combinators
|
|
258
|
+
* @experimental
|
|
259
|
+
*/
|
|
260
|
+
export const get = <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, input: string): A | undefined => {
|
|
261
|
+
if (self.ring.length === 0) {
|
|
262
|
+
return undefined
|
|
263
|
+
}
|
|
264
|
+
const index = getIndexForInput(self, Hash.string(input))[0]
|
|
265
|
+
const node = self.ring[index][1]!
|
|
266
|
+
return self.nodes.get(node)![0]
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Distributes `count` shards across the nodes in the ring, attempting to
|
|
271
|
+
* balance the number of shards allocated to each node. Returns undefined if
|
|
272
|
+
* the hashring has no elements with weight.
|
|
273
|
+
*
|
|
274
|
+
* @since 3.19.0
|
|
275
|
+
* @category Combinators
|
|
276
|
+
* @experimental
|
|
277
|
+
*/
|
|
278
|
+
export const getShards = <A extends PrimaryKey.PrimaryKey>(self: HashRing<A>, count: number): Array<A> | undefined => {
|
|
279
|
+
if (self.ring.length === 0) {
|
|
280
|
+
return undefined
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const shards = new Array<A>(count)
|
|
284
|
+
|
|
285
|
+
// for tracking how many shards have been allocated to each node
|
|
286
|
+
const allocations = new Map<string, number>()
|
|
287
|
+
// for tracking which shards still need to be allocated
|
|
288
|
+
const remaining = new Set<number>()
|
|
289
|
+
// for tracking which nodes have reached the max allocation
|
|
290
|
+
const exclude = new Set<string>()
|
|
291
|
+
|
|
292
|
+
// First pass - allocate the closest nodes, skipping nodes that have reached
|
|
293
|
+
// max
|
|
294
|
+
const distances = new Array<[shard: number, node: string, distance: number]>(count)
|
|
295
|
+
for (let shard = 0; shard < count; shard++) {
|
|
296
|
+
const hash = (shardHashes[shard] ??= Hash.string(`shard-${shard}`))
|
|
297
|
+
const [index, distance] = getIndexForInput(self, hash)
|
|
298
|
+
const node = self.ring[index][1]!
|
|
299
|
+
distances[shard] = [shard, node, distance]
|
|
300
|
+
remaining.add(shard)
|
|
301
|
+
}
|
|
302
|
+
distances.sort((a, b) => a[2] - b[2])
|
|
303
|
+
for (let i = 0; i < count; i++) {
|
|
304
|
+
const [shard, node] = distances[i]
|
|
305
|
+
if (exclude.has(node)) continue
|
|
306
|
+
const [value, weight] = self.nodes.get(node)!
|
|
307
|
+
shards[shard] = value
|
|
308
|
+
remaining.delete(shard)
|
|
309
|
+
const nodeCount = (allocations.get(node) ?? 0) + 1
|
|
310
|
+
allocations.set(node, nodeCount)
|
|
311
|
+
const maxPerNode = Math.max(1, Math.floor(count * (weight / self.totalWeightCache)))
|
|
312
|
+
if (nodeCount >= maxPerNode) {
|
|
313
|
+
exclude.add(node)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Second pass - allocate any remaining shards, skipping nodes that have
|
|
318
|
+
// reached max
|
|
319
|
+
let allAtMax = exclude.size === self.nodes.size
|
|
320
|
+
remaining.forEach((shard) => {
|
|
321
|
+
const index = getIndexForInput(self, shardHashes[shard], allAtMax ? undefined : exclude)[0]
|
|
322
|
+
const node = self.ring[index][1]
|
|
323
|
+
const [value, weight] = self.nodes.get(node)!
|
|
324
|
+
shards[shard] = value
|
|
325
|
+
|
|
326
|
+
if (allAtMax) return
|
|
327
|
+
const nodeCount = (allocations.get(node) ?? 0) + 1
|
|
328
|
+
allocations.set(node, nodeCount)
|
|
329
|
+
const maxPerNode = Math.max(1, Math.floor(count * (weight / self.totalWeightCache)))
|
|
330
|
+
if (nodeCount >= maxPerNode) {
|
|
331
|
+
exclude.add(node)
|
|
332
|
+
if (exclude.size === self.nodes.size) {
|
|
333
|
+
allAtMax = true
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
return shards
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const shardHashes: Array<number> = []
|
|
342
|
+
|
|
343
|
+
function getIndexForInput<A extends PrimaryKey.PrimaryKey>(
|
|
344
|
+
self: HashRing<A>,
|
|
345
|
+
hash: number,
|
|
346
|
+
exclude?: ReadonlySet<string> | undefined
|
|
347
|
+
): readonly [index: number, distance: number] {
|
|
348
|
+
const ring = self.ring
|
|
349
|
+
const len = ring.length
|
|
350
|
+
|
|
351
|
+
let mid: number
|
|
352
|
+
let lo = 0
|
|
353
|
+
let hi = len - 1
|
|
354
|
+
|
|
355
|
+
while (lo <= hi) {
|
|
356
|
+
mid = ((lo + hi) / 2) >>> 0
|
|
357
|
+
if (ring[mid][0] >= hash) {
|
|
358
|
+
hi = mid - 1
|
|
359
|
+
} else {
|
|
360
|
+
lo = mid + 1
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const a = lo === len ? lo - 1 : lo
|
|
364
|
+
const distA = Math.abs(ring[a][0] - hash)
|
|
365
|
+
if (exclude === undefined) {
|
|
366
|
+
const b = lo - 1
|
|
367
|
+
if (b < 0) {
|
|
368
|
+
return [a, distA]
|
|
369
|
+
}
|
|
370
|
+
const distB = Math.abs(ring[b][0] - hash)
|
|
371
|
+
return distA <= distB ? [a, distA] : [b, distB]
|
|
372
|
+
} else if (!exclude.has(ring[a][1])) {
|
|
373
|
+
return [a, distA]
|
|
374
|
+
}
|
|
375
|
+
const range = Math.max(lo, len - lo)
|
|
376
|
+
for (let i = 1; i < range; i++) {
|
|
377
|
+
let index = lo - i
|
|
378
|
+
if (index >= 0 && index < len && !exclude.has(ring[index][1])) {
|
|
379
|
+
return [index, Math.abs(ring[index][0] - hash)]
|
|
380
|
+
}
|
|
381
|
+
index = lo + i
|
|
382
|
+
if (index >= 0 && index < len && !exclude.has(ring[index][1])) {
|
|
383
|
+
return [index, Math.abs(ring[index][0] - hash)]
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return [a, distA]
|
|
387
|
+
}
|
package/src/Types.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* @since 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
type _TupleOf<T, N extends number, R extends Array<unknown>> =
|
|
7
|
+
type _TupleOf<T, N extends number, R extends Array<unknown>> = `${N}` extends `-${number}` ? never
|
|
8
|
+
: R["length"] extends N ? R
|
|
9
|
+
: _TupleOf<T, N, [T, ...R]>
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Represents a tuple with a fixed number of elements of type `T`.
|
package/src/index.ts
CHANGED
package/src/internal/layer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as Array from "../Array.js"
|
|
1
2
|
import * as Cause from "../Cause.js"
|
|
2
3
|
import * as Clock from "../Clock.js"
|
|
3
4
|
import * as Context from "../Context.js"
|
|
@@ -24,6 +25,7 @@ import type * as Types from "../Types.js"
|
|
|
24
25
|
import * as effect from "./core-effect.js"
|
|
25
26
|
import * as core from "./core.js"
|
|
26
27
|
import * as circular from "./effect/circular.js"
|
|
28
|
+
import * as ExecutionStrategy from "./executionStrategy.js"
|
|
27
29
|
import * as fiberRuntime from "./fiberRuntime.js"
|
|
28
30
|
import * as circularManagedRuntime from "./managedRuntime/circular.js"
|
|
29
31
|
import * as EffectOpCodes from "./opCodes/effect.js"
|
|
@@ -84,6 +86,7 @@ export type Primitive =
|
|
|
84
86
|
| ProvideTo
|
|
85
87
|
| ZipWith
|
|
86
88
|
| ZipWithPar
|
|
89
|
+
| MergeAll
|
|
87
90
|
|
|
88
91
|
/** @internal */
|
|
89
92
|
export type Op<Tag extends string, Body = {}> = Layer.Layer<unknown, unknown, unknown> & Body & {
|
|
@@ -168,6 +171,13 @@ export interface ZipWithPar extends
|
|
|
168
171
|
}>
|
|
169
172
|
{}
|
|
170
173
|
|
|
174
|
+
/** @internal */
|
|
175
|
+
export interface MergeAll extends
|
|
176
|
+
Op<OpCodes.OP_MERGE_ALL, {
|
|
177
|
+
readonly layers: Array.NonEmptyReadonlyArray<Layer.Layer<unknown>>
|
|
178
|
+
}>
|
|
179
|
+
{}
|
|
180
|
+
|
|
171
181
|
/** @internal */
|
|
172
182
|
export const isLayer = (u: unknown): u is Layer.Layer<unknown, unknown, unknown> => hasProperty(u, LayerTypeId)
|
|
173
183
|
|
|
@@ -444,15 +454,43 @@ const makeBuilder = <RIn, E, ROut>(
|
|
|
444
454
|
)
|
|
445
455
|
}
|
|
446
456
|
case "ZipWith": {
|
|
447
|
-
return core.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
457
|
+
return core.gen(function*() {
|
|
458
|
+
const parallelScope = yield* core.scopeFork(scope, ExecutionStrategy.parallel)
|
|
459
|
+
const firstScope = yield* core.scopeFork(parallelScope, ExecutionStrategy.sequential)
|
|
460
|
+
const secondScope = yield* core.scopeFork(parallelScope, ExecutionStrategy.sequential)
|
|
461
|
+
return (memoMap: Layer.MemoMap) =>
|
|
462
|
+
pipe(
|
|
463
|
+
memoMap.getOrElseMemoize(op.first, firstScope),
|
|
464
|
+
fiberRuntime.zipWithOptions(
|
|
465
|
+
memoMap.getOrElseMemoize(op.second, secondScope),
|
|
466
|
+
op.zipK,
|
|
467
|
+
{ concurrent: true }
|
|
468
|
+
)
|
|
454
469
|
)
|
|
455
|
-
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
case "MergeAll": {
|
|
473
|
+
const layers = op.layers
|
|
474
|
+
return core.map(
|
|
475
|
+
core.scopeFork(scope, ExecutionStrategy.parallel),
|
|
476
|
+
(parallelScope) => (memoMap: Layer.MemoMap) => {
|
|
477
|
+
const outputMap = new Map<string, unknown>()
|
|
478
|
+
return core.map(
|
|
479
|
+
fiberRuntime.forEachConcurrentDiscard(
|
|
480
|
+
layers,
|
|
481
|
+
core.fnUntraced(function*(layer) {
|
|
482
|
+
const scope = yield* core.scopeFork(parallelScope, ExecutionStrategy.sequential)
|
|
483
|
+
const context = yield* memoMap.getOrElseMemoize(layer, scope)
|
|
484
|
+
context.unsafeMap.forEach((value, key) => {
|
|
485
|
+
outputMap.set(key, value)
|
|
486
|
+
})
|
|
487
|
+
}),
|
|
488
|
+
false,
|
|
489
|
+
false
|
|
490
|
+
),
|
|
491
|
+
() => Context.unsafeMake(outputMap)
|
|
492
|
+
)
|
|
493
|
+
}
|
|
456
494
|
)
|
|
457
495
|
}
|
|
458
496
|
}
|
|
@@ -788,11 +826,10 @@ export const mergeAll = <
|
|
|
788
826
|
{ [k in keyof Layers]: Layer.Layer.Error<Layers[k]> }[number],
|
|
789
827
|
{ [k in keyof Layers]: Layer.Layer.Context<Layers[k]> }[number]
|
|
790
828
|
> => {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
return final as any
|
|
829
|
+
const mergeAll = Object.create(proto)
|
|
830
|
+
mergeAll._op_layer = OpCodes.OP_MERGE_ALL
|
|
831
|
+
mergeAll.layers = layers
|
|
832
|
+
return mergeAll as any
|
|
796
833
|
}
|
|
797
834
|
|
|
798
835
|
/** @internal */
|
|
@@ -46,6 +46,12 @@ export const OP_PROVIDE_MERGE = "ProvideMerge" as const
|
|
|
46
46
|
/** @internal */
|
|
47
47
|
export type OP_PROVIDE_MERGE = typeof OP_PROVIDE_MERGE
|
|
48
48
|
|
|
49
|
+
/** @internal */
|
|
50
|
+
export const OP_MERGE_ALL = "MergeAll" as const
|
|
51
|
+
|
|
52
|
+
/** @internal */
|
|
53
|
+
export type OP_MERGE_ALL = typeof OP_MERGE_ALL
|
|
54
|
+
|
|
49
55
|
/** @internal */
|
|
50
56
|
export const OP_ZIP_WITH = "ZipWith" as const
|
|
51
57
|
|
package/src/internal/version.ts
CHANGED