effect-browser 0.3.0-beta.7 → 0.3.0-beta.8
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/README.md +87 -19
- package/dist/{Supervisor-CLJaT7rG.d.mts → Supervisor-CYWFK3vM.d.mts} +105 -55
- package/dist/Supervisor-TNKNOVzu.mjs +599 -0
- package/dist/Supervisor-TNKNOVzu.mjs.map +1 -0
- package/dist/Supervisor.d.mts +2 -2
- package/dist/Supervisor.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/Supervisor.ts +384 -138
- package/src/internal/supervisor/lifecycle.ts +291 -140
- package/dist/Supervisor-BtEG93gR.mjs +0 -420
- package/dist/Supervisor-BtEG93gR.mjs.map +0 -1
|
@@ -28,6 +28,12 @@ export class Reopening extends Schema.TaggedClass<Reopening>()("Reopening", {})
|
|
|
28
28
|
/** Open: `browser` gives this generation until the next one opens. */
|
|
29
29
|
export class Open extends Schema.TaggedClass<Open>()("Open", {}) {}
|
|
30
30
|
|
|
31
|
+
/** No longer serving; holds end at this host-monotonic deadline or when their owners let go. */
|
|
32
|
+
export class Draining extends Schema.TaggedClass<Draining>()("Draining", {
|
|
33
|
+
reason: Schema.Literals(["rotated", "lost", "retired"]),
|
|
34
|
+
revokeAt: Schema.Finite,
|
|
35
|
+
}) {}
|
|
36
|
+
|
|
31
37
|
/** Lost: its connection dropped, or its provider ended its session, as `cause` says. */
|
|
32
38
|
export class Lost extends Schema.TaggedClass<Lost>()("Lost", {
|
|
33
39
|
cause: DisconnectCause,
|
|
@@ -53,7 +59,16 @@ export class Closed extends Schema.TaggedClass<Closed>()("Closed", {
|
|
|
53
59
|
*/
|
|
54
60
|
export class Kept extends Schema.TaggedClass<Kept>()("Kept", {}) {}
|
|
55
61
|
|
|
56
|
-
export const GenerationState = Schema.Union([
|
|
62
|
+
export const GenerationState = Schema.Union([
|
|
63
|
+
Opening,
|
|
64
|
+
Reopening,
|
|
65
|
+
Open,
|
|
66
|
+
Draining,
|
|
67
|
+
Lost,
|
|
68
|
+
Down,
|
|
69
|
+
Closed,
|
|
70
|
+
Kept,
|
|
71
|
+
]);
|
|
57
72
|
|
|
58
73
|
export type GenerationState = typeof GenerationState.Type;
|
|
59
74
|
|
|
@@ -63,168 +78,314 @@ export interface Live<A> {
|
|
|
63
78
|
readonly value: A;
|
|
64
79
|
}
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
export interface Held<A, H> extends Live<A> {
|
|
82
|
+
readonly holds: ReadonlySet<H>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Release ownership remains here until the provider and generation scope have finished. */
|
|
86
|
+
export interface Drain<A, H> {
|
|
87
|
+
readonly _tag: "Draining" | "Releasing" | "Leaving";
|
|
88
|
+
readonly live: Held<A, H>;
|
|
89
|
+
readonly ending: Draining;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** One serving generation, one shared acquisition, and prior generations still owned. */
|
|
93
|
+
export type State<A, H = unknown> = (
|
|
94
|
+
| { readonly _tag: "Opening"; readonly number: number; readonly serving: Held<A, H> | undefined }
|
|
95
|
+
| { readonly _tag: "Open"; readonly serving: Held<A, H> }
|
|
73
96
|
| {
|
|
74
97
|
readonly _tag: "Down";
|
|
75
98
|
readonly number: number;
|
|
76
99
|
readonly detail: string;
|
|
77
100
|
readonly cause: unknown;
|
|
78
|
-
readonly serving:
|
|
101
|
+
readonly serving: Held<A, H> | undefined;
|
|
79
102
|
}
|
|
80
|
-
| { readonly _tag: "Retired" }
|
|
103
|
+
| { readonly _tag: "Retired" }
|
|
104
|
+
) & { readonly draining: ReadonlyMap<number, Drain<A, H>> };
|
|
81
105
|
|
|
82
|
-
export type Input<A> =
|
|
106
|
+
export type Input<A, H = unknown> =
|
|
83
107
|
| { readonly _tag: "Opened"; readonly live: Live<A> }
|
|
84
|
-
/** The open of `number` failed for good. */
|
|
85
108
|
| {
|
|
86
109
|
readonly _tag: "Failed";
|
|
87
110
|
readonly number: number;
|
|
88
111
|
readonly detail: string;
|
|
89
112
|
readonly cause: unknown;
|
|
90
113
|
}
|
|
91
|
-
/** The open of `number` was stopped, as on retiring, before it opened. */
|
|
92
114
|
| { readonly _tag: "Abandoned"; readonly number: number }
|
|
93
115
|
| { readonly _tag: "Lost"; readonly number: number; readonly cause: DisconnectCause }
|
|
94
|
-
/** A caller asks for the next generation, or the rotation time of `due` came. */
|
|
95
116
|
| { readonly _tag: "Rotate"; readonly due?: number | undefined }
|
|
96
117
|
| { readonly _tag: "Retire" }
|
|
97
|
-
/** Stop as `Retire` does, but leave the serving generation running. */
|
|
98
118
|
| { readonly _tag: "Keep" }
|
|
99
|
-
| {
|
|
119
|
+
| {
|
|
120
|
+
readonly _tag: "Hold";
|
|
121
|
+
readonly number?: number | undefined;
|
|
122
|
+
readonly hold: H;
|
|
123
|
+
}
|
|
124
|
+
| { readonly _tag: "Let"; readonly number: number; readonly hold: H }
|
|
125
|
+
| { readonly _tag: "Revoke"; readonly number: number }
|
|
126
|
+
| {
|
|
127
|
+
readonly _tag: "Released";
|
|
128
|
+
readonly number: number;
|
|
129
|
+
readonly released?: Released | undefined;
|
|
130
|
+
};
|
|
100
131
|
|
|
101
132
|
export type Command<A> =
|
|
102
|
-
/** Open `number`, after the release of `after` has finished. */
|
|
103
133
|
| { readonly _tag: "Open"; readonly number: number; readonly after: number | undefined }
|
|
104
|
-
/** Watch a generation that opened for its loss and its rotation time. */
|
|
105
134
|
| { readonly _tag: "Watch"; readonly live: Live<A> }
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
/** Signal ending and arrange revocation. Repeated only when loss/retirement accelerates it. */
|
|
136
|
+
| { readonly _tag: "Drain"; readonly live: Live<A>; readonly ending: Draining }
|
|
137
|
+
/** Revoke/close holds, await their Let inputs, release the provider, then close its scope. */
|
|
138
|
+
| { readonly _tag: "Release"; readonly live: Live<A>; readonly ending: Draining }
|
|
139
|
+
| { readonly _tag: "Leave"; readonly live: Live<A>; readonly ending: Draining };
|
|
140
|
+
|
|
141
|
+
export interface Step<A, H = unknown> {
|
|
142
|
+
readonly state: State<A, H>;
|
|
113
143
|
readonly events: ReadonlyArray<readonly [number, GenerationState]>;
|
|
114
144
|
readonly commands: ReadonlyArray<Command<A>>;
|
|
115
|
-
/** For `Rotate`, the generation to wait for, unless retired. */
|
|
116
145
|
readonly reply?: number | undefined;
|
|
146
|
+
readonly borrowed?: Live<A> | undefined;
|
|
117
147
|
}
|
|
118
148
|
|
|
119
|
-
export const start = <A>(): Step<A> => ({
|
|
120
|
-
state: { _tag: "Opening", number: 1, serving: undefined },
|
|
149
|
+
export const start = <A, H = unknown>(): Step<A, H> => ({
|
|
150
|
+
state: { _tag: "Opening", number: 1, serving: undefined, draining: new Map() },
|
|
121
151
|
events: [[1, new Opening()]],
|
|
122
152
|
commands: [{ _tag: "Open", number: 1, after: undefined }],
|
|
123
153
|
});
|
|
124
154
|
|
|
125
|
-
const servingOf = <A>(state: State<A>) =>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
const servingOf = <A, H>(state: State<A, H>) =>
|
|
156
|
+
state._tag === "Retired" ? undefined : state.serving;
|
|
157
|
+
|
|
158
|
+
/** The holds still owned for a generation, including one whose release has begun. */
|
|
159
|
+
export const lookup = <A, H>(state: State<A, H>, number: number): Held<A, H> | undefined =>
|
|
160
|
+
servingOf(state)?.number === number ? servingOf(state) : state.draining.get(number)?.live;
|
|
161
|
+
|
|
162
|
+
/** A transition's local result builder; input state and its collections remain immutable. */
|
|
163
|
+
interface Change<A, H> {
|
|
164
|
+
readonly now: number;
|
|
165
|
+
readonly handover: number;
|
|
166
|
+
readonly exclusive: boolean;
|
|
167
|
+
state: State<A, H>;
|
|
168
|
+
events: Array<readonly [number, GenerationState]>;
|
|
169
|
+
commands: Array<Command<A>>;
|
|
170
|
+
reply?: number;
|
|
171
|
+
borrowed?: Live<A>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The sole writer of a drain's ending and phase; each release command is emitted once. */
|
|
175
|
+
const draining = <A, H>(change: Change<A, H>, drain: Drain<A, H>) => {
|
|
176
|
+
const before = change.state.draining.get(drain.live.number);
|
|
177
|
+
const { live, ending, _tag } = drain;
|
|
178
|
+
|
|
179
|
+
change.state = {
|
|
180
|
+
...change.state,
|
|
181
|
+
draining: new Map(change.state.draining).set(live.number, drain),
|
|
182
|
+
};
|
|
183
|
+
if (before?.ending !== ending) {
|
|
184
|
+
change.events.push([live.number, ending]);
|
|
185
|
+
change.commands.push({ _tag: "Drain", live, ending });
|
|
186
|
+
}
|
|
187
|
+
if (_tag !== "Draining" && before?._tag !== _tag)
|
|
188
|
+
change.commands.push({ _tag: _tag === "Leaving" ? "Leave" : "Release", live, ending });
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const end = <A, H>(
|
|
192
|
+
change: Change<A, H>,
|
|
193
|
+
live: Held<A, H>,
|
|
194
|
+
reason: Draining["reason"],
|
|
195
|
+
keep = false,
|
|
196
|
+
) => {
|
|
197
|
+
const ending = new Draining({
|
|
198
|
+
reason,
|
|
199
|
+
revokeAt: change.now + (reason === "rotated" ? change.handover : 0),
|
|
148
200
|
});
|
|
149
201
|
|
|
202
|
+
draining(change, {
|
|
203
|
+
live,
|
|
204
|
+
ending,
|
|
205
|
+
_tag: keep
|
|
206
|
+
? "Leaving"
|
|
207
|
+
: live.holds.size === 0 || ending.revokeAt <= change.now
|
|
208
|
+
? "Releasing"
|
|
209
|
+
: "Draining",
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const revoke = <A, H>(change: Change<A, H>, number: number, reason?: "lost" | "retired") => {
|
|
214
|
+
const drain = change.state.draining.get(number);
|
|
215
|
+
|
|
216
|
+
if (drain?._tag !== "Draining" || (reason === undefined && change.now < drain.ending.revokeAt))
|
|
217
|
+
return;
|
|
218
|
+
draining(change, {
|
|
219
|
+
...drain,
|
|
220
|
+
_tag: "Releasing",
|
|
221
|
+
ending: reason === undefined ? drain.ending : new Draining({ reason, revokeAt: change.now }),
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const holds = <A, H>(
|
|
226
|
+
change: Change<A, H>,
|
|
227
|
+
input: Extract<Input<A, H>, { readonly _tag: "Hold" | "Let" }>,
|
|
228
|
+
) => {
|
|
229
|
+
const number = input.number ?? servingOf(change.state)?.number;
|
|
230
|
+
const live = number === undefined ? undefined : lookup(change.state, number);
|
|
231
|
+
|
|
232
|
+
if (live === undefined) return;
|
|
233
|
+
const drain = change.state.draining.get(live.number);
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
input._tag === "Hold" &&
|
|
237
|
+
drain !== undefined &&
|
|
238
|
+
(drain._tag !== "Draining" || change.now >= drain.ending.revokeAt)
|
|
239
|
+
)
|
|
240
|
+
return;
|
|
241
|
+
if (live.holds.has(input.hold) === (input._tag === "Hold")) return;
|
|
242
|
+
const holds = new Set(live.holds);
|
|
243
|
+
|
|
244
|
+
if (input._tag === "Hold") {
|
|
245
|
+
holds.add(input.hold);
|
|
246
|
+
change.borrowed = live;
|
|
247
|
+
} else holds.delete(input.hold);
|
|
248
|
+
const held = { ...live, holds };
|
|
249
|
+
|
|
250
|
+
if (drain !== undefined)
|
|
251
|
+
draining(change, {
|
|
252
|
+
...drain,
|
|
253
|
+
live: held,
|
|
254
|
+
_tag: drain._tag === "Draining" && holds.size === 0 ? "Releasing" : drain._tag,
|
|
255
|
+
});
|
|
256
|
+
else if (change.state._tag !== "Retired") change.state = { ...change.state, serving: held };
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const next = <A, H>(change: Change<A, H>, number: number, reopen: boolean) => {
|
|
260
|
+
const { exclusive } = change;
|
|
261
|
+
const current = servingOf(change.state);
|
|
262
|
+
|
|
263
|
+
const after = exclusive
|
|
264
|
+
? (current?.number ?? change.state.draining.keys().next().value)
|
|
265
|
+
: undefined;
|
|
266
|
+
|
|
267
|
+
if (current !== undefined && (reopen || exclusive))
|
|
268
|
+
end(change, current, reopen ? "lost" : "rotated");
|
|
269
|
+
change.state = {
|
|
270
|
+
_tag: "Opening",
|
|
271
|
+
number,
|
|
272
|
+
serving: reopen || exclusive ? undefined : current,
|
|
273
|
+
draining: change.state.draining,
|
|
274
|
+
};
|
|
275
|
+
change.events.push([number, reopen ? new Reopening() : new Opening()]);
|
|
276
|
+
change.commands.push({ _tag: "Open", number, after });
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const lost = <A, H>(
|
|
280
|
+
change: Change<A, H>,
|
|
281
|
+
input: Extract<Input<A, H>, { readonly _tag: "Lost" }>,
|
|
282
|
+
) => {
|
|
283
|
+
const { state } = change;
|
|
284
|
+
const drain = state.draining.get(input.number);
|
|
285
|
+
const current = servingOf(state);
|
|
286
|
+
|
|
287
|
+
if (drain?._tag === "Draining") {
|
|
288
|
+
change.events.push([input.number, new Lost({ cause: input.cause })]);
|
|
289
|
+
revoke(change, input.number, "lost");
|
|
290
|
+
} else if (state._tag !== "Retired" && current?.number === input.number) {
|
|
291
|
+
change.events.push([input.number, new Lost({ cause: input.cause })]);
|
|
292
|
+
if (state._tag !== "Opening")
|
|
293
|
+
next(change, (state._tag === "Open" ? current.number : state.number) + 1, true);
|
|
294
|
+
else {
|
|
295
|
+
change.state = { ...state, serving: undefined };
|
|
296
|
+
end(change, current, "lost");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/** Pure admission and lifecycle changes. A rejected hold preserves state identity. */
|
|
302
|
+
export const transition = <A, H = unknown>(
|
|
303
|
+
state: State<A, H>,
|
|
304
|
+
input: Input<A, H>,
|
|
305
|
+
exclusive: boolean,
|
|
306
|
+
now = 0,
|
|
307
|
+
handover = 30_000,
|
|
308
|
+
): Step<A, H> => {
|
|
309
|
+
const change: Change<A, H> = {
|
|
310
|
+
state,
|
|
311
|
+
events: [],
|
|
312
|
+
commands: [],
|
|
313
|
+
exclusive,
|
|
314
|
+
handover,
|
|
315
|
+
now,
|
|
316
|
+
};
|
|
317
|
+
|
|
150
318
|
switch (input._tag) {
|
|
319
|
+
case "Hold":
|
|
320
|
+
case "Let":
|
|
321
|
+
holds(change, input);
|
|
322
|
+
break;
|
|
323
|
+
case "Revoke":
|
|
324
|
+
revoke(change, input.number);
|
|
325
|
+
break;
|
|
151
326
|
case "Opened": {
|
|
152
|
-
const { live
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (state._tag !== "Opening" || state.number !== live.number)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
state
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
...(state.serving === undefined ? [] : [release(state.serving)]),
|
|
164
|
-
],
|
|
165
|
-
};
|
|
327
|
+
const live = { ...input.live, holds: new Set<H>() };
|
|
328
|
+
|
|
329
|
+
if (lookup(state, live.number) !== undefined) break;
|
|
330
|
+
if (state._tag !== "Opening" || state.number !== live.number) end(change, live, "retired");
|
|
331
|
+
else {
|
|
332
|
+
change.events.push([live.number, new Open()]);
|
|
333
|
+
change.commands.push({ _tag: "Watch", live });
|
|
334
|
+
if (state.serving !== undefined) end(change, state.serving, "rotated");
|
|
335
|
+
change.state = { _tag: "Open", serving: live, draining: change.state.draining };
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
166
338
|
}
|
|
167
339
|
case "Failed": {
|
|
168
340
|
const { number, detail, cause } = input;
|
|
169
|
-
const events = [[number, new Down({ detail, cause })] as const];
|
|
170
|
-
|
|
171
|
-
if (state._tag !== "Opening" || state.number !== input.number) return { ...stay, events };
|
|
172
341
|
|
|
173
|
-
|
|
174
|
-
state
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
};
|
|
342
|
+
if (state._tag === "Opening" && state.number === number)
|
|
343
|
+
change.state = { ...state, _tag: "Down", detail, cause };
|
|
344
|
+
change.events.push([number, new Down({ detail, cause })]);
|
|
345
|
+
break;
|
|
178
346
|
}
|
|
179
347
|
case "Abandoned":
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
true,
|
|
196
|
-
lost,
|
|
197
|
-
true,
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
return { ...reopened, events: [...events, ...reopened.events] };
|
|
348
|
+
change.events.push([input.number, new Closed({})]);
|
|
349
|
+
break;
|
|
350
|
+
case "Released": {
|
|
351
|
+
const drain = state.draining.get(input.number);
|
|
352
|
+
|
|
353
|
+
if (drain === undefined || drain._tag === "Draining" || drain.live.holds.size > 0) break;
|
|
354
|
+
const draining = new Map(state.draining);
|
|
355
|
+
|
|
356
|
+
draining.delete(input.number);
|
|
357
|
+
change.state = { ...state, draining };
|
|
358
|
+
change.events.push([
|
|
359
|
+
input.number,
|
|
360
|
+
input.released === undefined ? new Kept() : new Closed({ released: input.released }),
|
|
361
|
+
]);
|
|
362
|
+
break;
|
|
201
363
|
}
|
|
364
|
+
case "Lost":
|
|
365
|
+
lost(change, input);
|
|
366
|
+
break;
|
|
202
367
|
case "Rotate": {
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
368
|
+
if (
|
|
369
|
+
state._tag === "Retired" ||
|
|
370
|
+
(input.due !== undefined && (state._tag !== "Open" || state.serving.number !== input.due))
|
|
371
|
+
)
|
|
372
|
+
break;
|
|
373
|
+
change.reply =
|
|
374
|
+
state._tag === "Opening"
|
|
375
|
+
? state.number
|
|
376
|
+
: (state._tag === "Open" ? state.serving.number : state.number) + 1;
|
|
377
|
+
if (state._tag !== "Opening") next(change, change.reply, false);
|
|
378
|
+
break;
|
|
211
379
|
}
|
|
380
|
+
case "Keep":
|
|
212
381
|
case "Retire":
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (current === undefined) return { state: { _tag: "Retired" }, events: [], commands: [] };
|
|
218
|
-
|
|
219
|
-
return input._tag === "Retire"
|
|
220
|
-
? { state: { _tag: "Retired" }, events: [], commands: [release(current)] }
|
|
221
|
-
: {
|
|
222
|
-
state: { _tag: "Retired" },
|
|
223
|
-
events: [[current.number, new Kept()]],
|
|
224
|
-
commands: [{ _tag: "Leave", live: current }],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
382
|
+
if (state._tag === "Retired") break;
|
|
383
|
+
for (const number of state.draining.keys()) revoke(change, number, "retired");
|
|
384
|
+
if (state.serving !== undefined) end(change, state.serving, "retired", input._tag === "Keep");
|
|
385
|
+
change.state = { _tag: "Retired", draining: change.state.draining };
|
|
227
386
|
}
|
|
387
|
+
|
|
388
|
+
return change;
|
|
228
389
|
};
|
|
229
390
|
|
|
230
391
|
export type Resolution<A> =
|
|
@@ -236,30 +397,20 @@ export type Resolution<A> =
|
|
|
236
397
|
readonly cause?: unknown;
|
|
237
398
|
};
|
|
238
399
|
|
|
239
|
-
/**
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
*/
|
|
243
|
-
export const resolve = <A>(
|
|
244
|
-
state: State<A>,
|
|
400
|
+
/** An unheld browser resolution; use obtains its hold atomically through Hold instead. */
|
|
401
|
+
export const resolve = <A, H>(
|
|
402
|
+
state: State<A, H>,
|
|
245
403
|
target: number | undefined,
|
|
246
404
|
): Resolution<A> | undefined => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
case "Down":
|
|
259
|
-
if (target === undefined && state.serving !== undefined) return serve(state.serving);
|
|
260
|
-
|
|
261
|
-
return target === undefined || state.number >= target
|
|
262
|
-
? { _tag: "Unavailable", reason: "down", detail: state.detail, cause: state.cause }
|
|
263
|
-
: undefined;
|
|
264
|
-
}
|
|
405
|
+
if (state._tag === "Retired")
|
|
406
|
+
return { _tag: "Unavailable", reason: "retired", detail: "the supervisor was retired" };
|
|
407
|
+
if (
|
|
408
|
+
state.serving !== undefined &&
|
|
409
|
+
(target === undefined || (state._tag === "Open" && state.serving.number >= target))
|
|
410
|
+
)
|
|
411
|
+
return { _tag: "Serve", live: state.serving };
|
|
412
|
+
if (state._tag === "Down" && (target === undefined || state.number >= target))
|
|
413
|
+
return { _tag: "Unavailable", reason: "down", detail: state.detail, cause: state.cause };
|
|
414
|
+
|
|
415
|
+
return undefined;
|
|
265
416
|
};
|