@effect-app/infra 4.0.0-beta.258 → 4.0.0-beta.259
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 +7 -0
- package/dist/ClusterCosmos.d.ts +64 -0
- package/dist/ClusterCosmos.d.ts.map +1 -0
- package/dist/ClusterCosmos.js +487 -0
- package/docs/cluster-storage.md +26 -0
- package/docs/workflow-engine.md +262 -0
- package/package.json +2 -2
- package/run.sh +1 -0
- package/src/ClusterCosmos.ts +954 -0
- package/test/cluster-cosmos.test.ts +406 -0
- package/test/dist/cluster-cosmos.test.d.ts.map +1 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { assert, describe, expect, it } from "@effect/vitest"
|
|
2
|
+
import { Context, Effect, Exit, Fiber, Latch, Layer, Option, Redacted, Schema } from "effect"
|
|
3
|
+
import { TestClock } from "effect/testing"
|
|
4
|
+
import { ClusterSchema, Entity, EntityAddress, EntityId, EntityType, Envelope, Message, MessageStorage, Reply, Runner, RunnerAddress, RunnerHealth, Runners, RunnerStorage, ShardId, Sharding, ShardingConfig, Snowflake } from "effect/unstable/cluster"
|
|
5
|
+
import { Headers } from "effect/unstable/http"
|
|
6
|
+
import { Rpc, RpcSchema } from "effect/unstable/rpc"
|
|
7
|
+
import { layerCosmos } from "../src/ClusterCosmos.js"
|
|
8
|
+
|
|
9
|
+
const cosmosUrl = process.env["COSMOS_TEST_URL"]
|
|
10
|
+
const cosmosDb = process.env["COSMOS_TEST_DB"] ?? "cluster-test"
|
|
11
|
+
const testRunId = `${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}`
|
|
12
|
+
const runnerPortBase = 10000 + Date.now() % 40000
|
|
13
|
+
|
|
14
|
+
const layerFor = () =>
|
|
15
|
+
layerCosmos({
|
|
16
|
+
url: Redacted.make(cosmosUrl ?? ""),
|
|
17
|
+
dbName: cosmosDb,
|
|
18
|
+
prefix: "test-cluster-"
|
|
19
|
+
})
|
|
20
|
+
.pipe(
|
|
21
|
+
Layer.provideMerge(Snowflake.layerGenerator),
|
|
22
|
+
Layer.provide(ShardingConfig.layerDefaults)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
describe.skipIf(!cosmosUrl)("ClusterCosmos MessageStorage", () => {
|
|
26
|
+
it.effect("deduplicates keyed requests and returns the last reply", () =>
|
|
27
|
+
Effect
|
|
28
|
+
.gen(function*() {
|
|
29
|
+
const storage = yield* MessageStorage.MessageStorage
|
|
30
|
+
const shardId = testShardId("message-duplicate")
|
|
31
|
+
const primaryKey = `primary/${testRunId}\\with?illegal#chars`
|
|
32
|
+
const request = yield* makeStreamRequest(primaryKey, shardId)
|
|
33
|
+
|
|
34
|
+
const saved = yield* storage.saveRequest(request)
|
|
35
|
+
assert.strictEqual(saved._tag, "Success")
|
|
36
|
+
|
|
37
|
+
const chunk = yield* makeChunkReply(request, 0)
|
|
38
|
+
yield* storage.saveReply(chunk)
|
|
39
|
+
|
|
40
|
+
const duplicateWithChunk = yield* storage.saveRequest(
|
|
41
|
+
yield* makeStreamRequest(primaryKey, shardId)
|
|
42
|
+
)
|
|
43
|
+
assert(duplicateWithChunk._tag === "Duplicate" && Option.isSome(duplicateWithChunk.lastReceivedReply))
|
|
44
|
+
assert.strictEqual(duplicateWithChunk.lastReceivedReply.value._tag, "Chunk")
|
|
45
|
+
|
|
46
|
+
const ackChunk = yield* makeAckChunk(request, chunk)
|
|
47
|
+
yield* storage.saveEnvelope(ackChunk)
|
|
48
|
+
const repliesAfterAck = yield* storage.repliesFor([request])
|
|
49
|
+
assert.strictEqual(repliesAfterAck.length, 0)
|
|
50
|
+
|
|
51
|
+
yield* storage.saveReply(yield* makeStreamReply(request))
|
|
52
|
+
const duplicateWithExit = yield* storage.saveRequest(
|
|
53
|
+
yield* makeStreamRequest(primaryKey, shardId)
|
|
54
|
+
)
|
|
55
|
+
assert(duplicateWithExit._tag === "Duplicate" && Option.isSome(duplicateWithExit.lastReceivedReply))
|
|
56
|
+
assert.strictEqual(duplicateWithExit.lastReceivedReply.value._tag, "WithExit")
|
|
57
|
+
})
|
|
58
|
+
.pipe(Effect.provide(layerFor())))
|
|
59
|
+
|
|
60
|
+
it.effect("marks reads, resets shards, and excludes completed requests", () =>
|
|
61
|
+
Effect
|
|
62
|
+
.gen(function*() {
|
|
63
|
+
const storage = yield* MessageStorage.MessageStorage
|
|
64
|
+
const shardId = testShardId("message-unprocessed")
|
|
65
|
+
const request1 = yield* makeRequest({ payload: { id: 1 }, shardId })
|
|
66
|
+
const request2 = yield* makeRequest({ payload: { id: 2 }, shardId })
|
|
67
|
+
assert.strictEqual((yield* storage.saveRequest(request1))._tag, "Success")
|
|
68
|
+
assert.strictEqual((yield* storage.saveRequest(request2))._tag, "Success")
|
|
69
|
+
|
|
70
|
+
let messages = yield* storage.unprocessedMessages([request1.envelope.address.shardId])
|
|
71
|
+
assert.deepStrictEqual(messages.map((message) => requestPayloadId(message)).sort(), [1, 2])
|
|
72
|
+
|
|
73
|
+
messages = yield* storage.unprocessedMessages([request1.envelope.address.shardId])
|
|
74
|
+
assert.strictEqual(messages.length, 0)
|
|
75
|
+
|
|
76
|
+
yield* storage.resetShards([request1.envelope.address.shardId])
|
|
77
|
+
messages = yield* storage.unprocessedMessages([request1.envelope.address.shardId])
|
|
78
|
+
assert.deepStrictEqual(messages.map((message) => requestPayloadId(message)).sort(), [1, 2])
|
|
79
|
+
|
|
80
|
+
yield* storage.saveReply(yield* makeReply(request1))
|
|
81
|
+
yield* storage.resetShards([request1.envelope.address.shardId])
|
|
82
|
+
messages = yield* storage.unprocessedMessages([request1.envelope.address.shardId])
|
|
83
|
+
assert.deepStrictEqual(messages.map((message) => requestPayloadId(message)), [2])
|
|
84
|
+
})
|
|
85
|
+
.pipe(Effect.provide(layerFor())))
|
|
86
|
+
|
|
87
|
+
it.effect("notifies registered reply handlers", () =>
|
|
88
|
+
Effect
|
|
89
|
+
.gen(function*() {
|
|
90
|
+
const storage = yield* MessageStorage.MessageStorage
|
|
91
|
+
const latch = yield* Latch.make()
|
|
92
|
+
const request = yield* makeRequest({ shardId: testShardId("message-handler") })
|
|
93
|
+
yield* storage.saveRequest(request)
|
|
94
|
+
|
|
95
|
+
const fiber = yield* storage
|
|
96
|
+
.registerReplyHandler(
|
|
97
|
+
new Message.OutgoingRequest({
|
|
98
|
+
...request,
|
|
99
|
+
respond: () => latch.open
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
.pipe(Effect.forkChild)
|
|
103
|
+
|
|
104
|
+
yield* TestClock.adjust(1)
|
|
105
|
+
yield* storage.saveReply(yield* makeReply(request))
|
|
106
|
+
yield* latch.await
|
|
107
|
+
yield* Fiber.await(fiber)
|
|
108
|
+
})
|
|
109
|
+
.pipe(Effect.provide(layerFor())))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe.skipIf(!cosmosUrl)("ClusterCosmos RunnerStorage", () => {
|
|
113
|
+
it.effect("registers runners and tracks health", () =>
|
|
114
|
+
Effect
|
|
115
|
+
.gen(function*() {
|
|
116
|
+
const storage = yield* RunnerStorage.RunnerStorage
|
|
117
|
+
const runnerAddress = testRunnerAddress(1)
|
|
118
|
+
const runner = Runner.make({
|
|
119
|
+
address: runnerAddress,
|
|
120
|
+
groups: ["default"],
|
|
121
|
+
weight: 1
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const machineId1 = yield* storage.register(runner, true)
|
|
125
|
+
const machineId2 = yield* storage.register(runner, true)
|
|
126
|
+
assert.deepStrictEqual(machineId2, machineId1)
|
|
127
|
+
expect(runnerStatus(yield* storage.getRunners, runnerAddress)).toEqual([runner, true])
|
|
128
|
+
|
|
129
|
+
yield* storage.setRunnerHealth(runnerAddress, false)
|
|
130
|
+
expect(runnerStatus(yield* storage.getRunners, runnerAddress)).toEqual([runner, false])
|
|
131
|
+
|
|
132
|
+
yield* storage.unregister(runnerAddress)
|
|
133
|
+
expect(runnerStatus(yield* storage.getRunners, runnerAddress)).toBeUndefined()
|
|
134
|
+
})
|
|
135
|
+
.pipe(Effect.provide(layerFor())))
|
|
136
|
+
|
|
137
|
+
it.effect("acquires, refreshes, releases, and re-acquires shard locks", () =>
|
|
138
|
+
Effect
|
|
139
|
+
.gen(function*() {
|
|
140
|
+
const storage = yield* RunnerStorage.RunnerStorage
|
|
141
|
+
const runnerAddress1 = testRunnerAddress(2)
|
|
142
|
+
const runnerAddress2 = testRunnerAddress(3)
|
|
143
|
+
const shards = [
|
|
144
|
+
testShardId("runner-locks", 1),
|
|
145
|
+
testShardId("runner-locks", 2),
|
|
146
|
+
testShardId("runner-locks", 3)
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
let acquired = yield* storage.acquire(runnerAddress1, shards)
|
|
150
|
+
assert.deepStrictEqual(acquired.map((shard) => shard.id), [1, 2, 3])
|
|
151
|
+
|
|
152
|
+
acquired = yield* storage.acquire(runnerAddress2, shards)
|
|
153
|
+
assert.deepStrictEqual(acquired.map((shard) => shard.id), [])
|
|
154
|
+
|
|
155
|
+
const refreshed = yield* storage.refresh(runnerAddress1, shards)
|
|
156
|
+
assert.deepStrictEqual(refreshed.map((shard) => shard.id), [1, 2, 3])
|
|
157
|
+
|
|
158
|
+
yield* storage.release(runnerAddress1, testShardId("runner-locks", 2))
|
|
159
|
+
acquired = yield* storage.acquire(runnerAddress2, shards)
|
|
160
|
+
assert.deepStrictEqual(acquired.map((shard) => shard.id), [2])
|
|
161
|
+
|
|
162
|
+
yield* storage.releaseAll(runnerAddress1)
|
|
163
|
+
acquired = yield* storage.acquire(runnerAddress2, shards)
|
|
164
|
+
assert.deepStrictEqual(acquired.map((shard) => shard.id), [1, 2, 3])
|
|
165
|
+
})
|
|
166
|
+
.pipe(Effect.provide(layerFor())))
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe.skipIf(!cosmosUrl)("ClusterCosmos Sharding RPC", () => {
|
|
170
|
+
it.effect("runs persisted entity RPCs through Cosmos-backed cluster storage", () =>
|
|
171
|
+
Effect
|
|
172
|
+
.gen(function*() {
|
|
173
|
+
yield* TestClock.adjust(1)
|
|
174
|
+
const sharding = yield* Sharding.Sharding
|
|
175
|
+
const makeClient = yield* CosmosRpcEntity.client
|
|
176
|
+
const entityId = `entity/${testRunId}\\with?illegal#chars`
|
|
177
|
+
const shardId = sharding.getShardId(EntityId.make(entityId), testShardGroup("rpc"))
|
|
178
|
+
yield* waitForShard(sharding, shardId)
|
|
179
|
+
assert.isTrue(sharding.hasShardId(shardId))
|
|
180
|
+
const client = makeClient(entityId)
|
|
181
|
+
|
|
182
|
+
const user = yield* client.GetCosmosUser({ id: 42 })
|
|
183
|
+
expect(user).toEqual(new CosmosRpcUser({ id: 42, name: "User 42" }))
|
|
184
|
+
|
|
185
|
+
const primaryKey = `rpc/${testRunId}\\with?illegal#chars`
|
|
186
|
+
const first = yield* client.CosmosRequestWithKey({ key: primaryKey })
|
|
187
|
+
const duplicate = yield* client.CosmosRequestWithKey({ key: primaryKey })
|
|
188
|
+
|
|
189
|
+
assert.strictEqual(first, primaryKey)
|
|
190
|
+
assert.strictEqual(duplicate, primaryKey)
|
|
191
|
+
})
|
|
192
|
+
.pipe(Effect.provide(clusterRpcLayer("rpc"))), 20000)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const GetUserRpc = Rpc.make("GetUser", {
|
|
196
|
+
payload: { id: Schema.Number }
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
class CosmosRpcUser extends Schema.Class<CosmosRpcUser>("CosmosRpcUser")({
|
|
200
|
+
id: Schema.Number,
|
|
201
|
+
name: Schema.String
|
|
202
|
+
}) {}
|
|
203
|
+
|
|
204
|
+
const CosmosRpcEntity = Entity
|
|
205
|
+
.make("CosmosRpcEntity", [
|
|
206
|
+
Rpc.make("GetCosmosUser", {
|
|
207
|
+
success: CosmosRpcUser,
|
|
208
|
+
payload: { id: Schema.Number }
|
|
209
|
+
}),
|
|
210
|
+
Rpc.make("CosmosRequestWithKey", {
|
|
211
|
+
success: Schema.String,
|
|
212
|
+
payload: { key: Schema.String },
|
|
213
|
+
primaryKey: ({ key }) => key
|
|
214
|
+
})
|
|
215
|
+
])
|
|
216
|
+
.annotate(ClusterSchema.ShardGroup, () => testShardGroup("rpc"))
|
|
217
|
+
.annotateRpcs(ClusterSchema.Persisted, true)
|
|
218
|
+
|
|
219
|
+
const CosmosRpcEntityLayer = CosmosRpcEntity.toLayer(
|
|
220
|
+
Effect.succeed(
|
|
221
|
+
CosmosRpcEntity.of({
|
|
222
|
+
GetCosmosUser: (envelope) =>
|
|
223
|
+
Effect.succeed(new CosmosRpcUser({ id: envelope.payload.id, name: `User ${envelope.payload.id}` })),
|
|
224
|
+
CosmosRequestWithKey: (envelope) => Effect.succeed(envelope.payload.key)
|
|
225
|
+
})
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
class StreamRpc extends Rpc.make("StreamTest", {
|
|
230
|
+
success: RpcSchema.Stream(Schema.Void, Schema.Never),
|
|
231
|
+
payload: {
|
|
232
|
+
id: Schema.String
|
|
233
|
+
},
|
|
234
|
+
primaryKey: (value) => value.id.toString()
|
|
235
|
+
}) {}
|
|
236
|
+
|
|
237
|
+
const makeRequest = Effect.fnUntraced(function*(options?: {
|
|
238
|
+
readonly payload?: { readonly id: number }
|
|
239
|
+
readonly shardId?: ShardId.ShardId
|
|
240
|
+
}) {
|
|
241
|
+
const snowflake = yield* Snowflake.Generator
|
|
242
|
+
return new Message.OutgoingRequest({
|
|
243
|
+
envelope: Envelope.makeRequest<typeof GetUserRpc>({
|
|
244
|
+
requestId: snowflake.nextUnsafe(),
|
|
245
|
+
address: EntityAddress.make({
|
|
246
|
+
shardId: options?.shardId ?? testShardId("default"),
|
|
247
|
+
entityType: EntityType.make("test"),
|
|
248
|
+
entityId: EntityId.make("1")
|
|
249
|
+
}),
|
|
250
|
+
tag: GetUserRpc._tag,
|
|
251
|
+
payload: options?.payload ?? { id: 123 },
|
|
252
|
+
traceId: "noop",
|
|
253
|
+
spanId: "noop",
|
|
254
|
+
sampled: false,
|
|
255
|
+
headers: Headers.empty
|
|
256
|
+
}),
|
|
257
|
+
annotations: GetUserRpc.annotations,
|
|
258
|
+
context: Context.empty(),
|
|
259
|
+
rpc: GetUserRpc,
|
|
260
|
+
lastReceivedReply: Option.none(),
|
|
261
|
+
respond() {
|
|
262
|
+
return Effect.void
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const makeStreamRequest = Effect.fnUntraced(function*(id: string, shardId = testShardId("stream")) {
|
|
268
|
+
const snowflake = yield* Snowflake.Generator
|
|
269
|
+
return new Message.OutgoingRequest({
|
|
270
|
+
envelope: Envelope.makeRequest<typeof StreamRpc>({
|
|
271
|
+
requestId: snowflake.nextUnsafe(),
|
|
272
|
+
address: EntityAddress.make({
|
|
273
|
+
shardId,
|
|
274
|
+
entityType: EntityType.make("test"),
|
|
275
|
+
entityId: EntityId.make("1")
|
|
276
|
+
}),
|
|
277
|
+
tag: StreamRpc._tag,
|
|
278
|
+
payload: StreamRpc.payloadSchema.make({ id }),
|
|
279
|
+
traceId: "noop",
|
|
280
|
+
spanId: "noop",
|
|
281
|
+
sampled: false,
|
|
282
|
+
headers: Headers.empty
|
|
283
|
+
}),
|
|
284
|
+
annotations: StreamRpc.annotations,
|
|
285
|
+
context: Context.empty(),
|
|
286
|
+
rpc: StreamRpc,
|
|
287
|
+
lastReceivedReply: Option.none(),
|
|
288
|
+
respond() {
|
|
289
|
+
return Effect.void
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const makeReply = Effect.fnUntraced(function*(request: Message.OutgoingRequest<typeof GetUserRpc>) {
|
|
295
|
+
const snowflake = yield* Snowflake.Generator
|
|
296
|
+
return new Reply.ReplyWithContext({
|
|
297
|
+
reply: new Reply.WithExit<typeof GetUserRpc>({
|
|
298
|
+
id: snowflake.nextUnsafe(),
|
|
299
|
+
requestId: request.envelope.requestId,
|
|
300
|
+
exit: Exit.void
|
|
301
|
+
}),
|
|
302
|
+
context: request.context,
|
|
303
|
+
rpc: request.rpc
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const makeStreamReply = Effect.fnUntraced(function*(request: Message.OutgoingRequest<typeof StreamRpc>) {
|
|
308
|
+
const snowflake = yield* Snowflake.Generator
|
|
309
|
+
return new Reply.ReplyWithContext({
|
|
310
|
+
reply: new Reply.WithExit<typeof StreamRpc>({
|
|
311
|
+
id: snowflake.nextUnsafe(),
|
|
312
|
+
requestId: request.envelope.requestId,
|
|
313
|
+
exit: Exit.void
|
|
314
|
+
}),
|
|
315
|
+
context: request.context,
|
|
316
|
+
rpc: request.rpc
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const makeAckChunk = Effect.fnUntraced(function*(
|
|
321
|
+
request: Message.OutgoingRequest<typeof StreamRpc>,
|
|
322
|
+
chunk: Reply.ReplyWithContext<typeof StreamRpc>
|
|
323
|
+
) {
|
|
324
|
+
const snowflake = yield* Snowflake.Generator
|
|
325
|
+
return new Message.OutgoingEnvelope({
|
|
326
|
+
envelope: new Envelope.AckChunk({
|
|
327
|
+
id: snowflake.nextUnsafe(),
|
|
328
|
+
address: request.envelope.address,
|
|
329
|
+
requestId: chunk.reply.requestId,
|
|
330
|
+
replyId: chunk.reply.id
|
|
331
|
+
}),
|
|
332
|
+
rpc: request.rpc
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const makeChunkReply = Effect.fnUntraced(function*(
|
|
337
|
+
request: Message.OutgoingRequest<typeof StreamRpc>,
|
|
338
|
+
sequence: number
|
|
339
|
+
) {
|
|
340
|
+
const snowflake = yield* Snowflake.Generator
|
|
341
|
+
return new Reply.ReplyWithContext({
|
|
342
|
+
reply: new Reply.Chunk<typeof StreamRpc>({
|
|
343
|
+
id: snowflake.nextUnsafe(),
|
|
344
|
+
requestId: request.envelope.requestId,
|
|
345
|
+
sequence,
|
|
346
|
+
values: [undefined]
|
|
347
|
+
}),
|
|
348
|
+
context: request.context,
|
|
349
|
+
rpc: request.rpc
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const requestPayloadId = (message: Message.Incoming<never>) => {
|
|
354
|
+
if (message.envelope._tag !== "Request") {
|
|
355
|
+
throw new Error(`Expected Request envelope`)
|
|
356
|
+
}
|
|
357
|
+
const envelope = message.envelope
|
|
358
|
+
assert(typeof envelope.payload === "object" && envelope.payload !== null)
|
|
359
|
+
assert("id" in envelope.payload)
|
|
360
|
+
assert.strictEqual(typeof envelope.payload.id, "number")
|
|
361
|
+
return envelope.payload.id
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const testShardId = (label: string, id = 1) => ShardId.make(`cluster-cosmos-${testRunId}-${label}`, id)
|
|
365
|
+
|
|
366
|
+
const testShardGroup = (label: string) => `cluster-cosmos-${testRunId}-${label}`
|
|
367
|
+
|
|
368
|
+
const testRunnerAddress = (offset: number) => RunnerAddress.make("localhost", runnerPortBase + offset)
|
|
369
|
+
|
|
370
|
+
const runnerStatus = (
|
|
371
|
+
runners: ReadonlyArray<readonly [Runner.Runner, boolean]>,
|
|
372
|
+
address: RunnerAddress.RunnerAddress
|
|
373
|
+
) => runners.find(([runner]) => runner.address.host === address.host && runner.address.port === address.port)
|
|
374
|
+
|
|
375
|
+
const clusterRpcLayer = (label: string) => {
|
|
376
|
+
const shardGroup = testShardGroup(label)
|
|
377
|
+
return CosmosRpcEntityLayer.pipe(
|
|
378
|
+
Layer.provideMerge(Sharding.layer),
|
|
379
|
+
Layer.provide(Runners.layerNoop),
|
|
380
|
+
Layer.provide(RunnerHealth.layerNoop),
|
|
381
|
+
Layer.provide(layerCosmos({
|
|
382
|
+
url: Redacted.make(cosmosUrl ?? ""),
|
|
383
|
+
dbName: cosmosDb,
|
|
384
|
+
prefix: "test-cluster-"
|
|
385
|
+
})),
|
|
386
|
+
Layer.provide(ShardingConfig.layer({
|
|
387
|
+
runnerAddress: Option.some(testRunnerAddress(10)),
|
|
388
|
+
shardsPerGroup: 1,
|
|
389
|
+
availableShardGroups: [shardGroup],
|
|
390
|
+
assignedShardGroups: [shardGroup],
|
|
391
|
+
entityTerminationTimeout: 0,
|
|
392
|
+
entityMessagePollInterval: 50,
|
|
393
|
+
entityReplyPollInterval: 50,
|
|
394
|
+
refreshAssignmentsInterval: 0,
|
|
395
|
+
sendRetryInterval: 50
|
|
396
|
+
}))
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const waitForShard = (sharding: Sharding.Sharding["Service"], shardId: ShardId.ShardId) =>
|
|
401
|
+
Effect.gen(function*() {
|
|
402
|
+
for (let i = 0; i < 30; i++) {
|
|
403
|
+
if (sharding.hasShardId(shardId)) return
|
|
404
|
+
yield* Effect.promise<void>(() => new Promise((resolve) => setTimeout(resolve, 100)))
|
|
405
|
+
}
|
|
406
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster-cosmos.test.d.ts","sourceRoot":"","sources":["../cluster-cosmos.test.ts"],"names":[],"mappings":""}
|