-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathapi.ts
453 lines (392 loc) · 12.3 KB
/
api.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import { JsonObject, JsonValue, schema } from '@angular-devkit/core';
import { Observable, Observer } from 'rxjs';
import { DeepReadonly } from './types';
/**
* A job name is just a string (needs to be serializable).
*/
export type JobName = string;
/**
* The job handler function, which is a method that's executed for the job.
*/
export interface JobHandler<
ArgT extends JsonValue,
InputT extends JsonValue,
OutputT extends JsonValue,
> {
(
argument: ArgT,
context: JobHandlerContext<ArgT, InputT, OutputT>,
): Observable<JobOutboundMessage<OutputT>>;
jobDescription: Partial<JobDescription>;
}
/**
* The context in which the job is run.
*/
export interface JobHandlerContext<
MinimumArgumentValueT extends JsonValue = JsonValue,
MinimumInputValueT extends JsonValue = JsonValue,
MinimumOutputValueT extends JsonValue = JsonValue,
> {
readonly description: JobDescription;
readonly scheduler: Scheduler<JsonValue, JsonValue, JsonValue>;
// In this context, JsonValue is comparable to `any`.
readonly dependencies: Job<JsonValue, JsonValue, JsonValue>[];
readonly inboundBus: Observable<JobInboundMessage<MinimumInputValueT>>;
}
/**
* Metadata associated with a job.
*/
export interface JobDescription extends JsonObject {
readonly name: JobName;
readonly argument: DeepReadonly<schema.JsonSchema>;
readonly input: DeepReadonly<schema.JsonSchema>;
readonly output: DeepReadonly<schema.JsonSchema>;
}
/**
* Messages that can be sent TO a job. The job needs to listen to those.
*/
export enum JobInboundMessageKind {
Ping = 'ip',
Stop = 'is',
// Channel specific messages.
Input = 'in',
// Input channel does not allow completion / error. Erroring this will just close the Subject
// but not notify the job.
}
/** Base interface for the all job inbound messages. */
export interface JobInboundMessageBase extends JsonObject {
/**
* The kind of message this is.
*/
readonly kind: JobInboundMessageKind;
}
/**
* A ping to the job. The job should reply with a pong as soon as possible.
*/
export interface JobInboundMessagePing extends JobInboundMessageBase {
readonly kind: JobInboundMessageKind.Ping;
/**
* An ID that should be returned in the corresponding Pong.
*/
readonly id: number;
}
/**
* Stop the job. This is handled by the job itself and jobs might not handle it. It will also
* unsubscribe from the Observable<>.
* This is equivalent to SIGTERM.
*/
export interface JobInboundMessageStop extends JobInboundMessageBase {
readonly kind: JobInboundMessageKind.Stop;
}
/**
* A Job wants to send a message to a channel. This can be marshaled, and the Job object
* has helpers to transform this into an observable. The context also can create RxJS subjects that
* marshall messages through a channel.
*/
export interface JobInboundMessageInput<InputT extends JsonValue> extends JobInboundMessageBase {
readonly kind: JobInboundMessageKind.Input;
/**
* The input being sent to the job.
*/
readonly value: InputT;
}
export type JobInboundMessage<InputT extends JsonValue> =
| JobInboundMessagePing
| JobInboundMessageStop
| JobInboundMessageInput<InputT>;
/**
* Kind of messages that can be outputted from a job.
*/
export enum JobOutboundMessageKind {
// Lifecycle specific messages.
OnReady = 'c',
Start = 's',
End = 'e',
Pong = 'p',
// Feedback messages.
Output = 'o',
// Channel specific messages.
ChannelCreate = 'cn',
ChannelMessage = 'cm',
ChannelError = 'ce',
ChannelComplete = 'cc',
}
/** Base interface for the all job messages. */
export interface JobOutboundMessageBase {
/**
* The job description.
*/
readonly description: JobDescription;
/**
* The kind of message this is.
*/
readonly kind: JobOutboundMessageKind;
}
/**
* The job has been created and will validate its input.
*/
export interface JobOutboundMessageOnReady extends JobOutboundMessageBase {
readonly kind: JobOutboundMessageKind.OnReady;
}
/**
* The job started. This is done by the job itself.
*/
export interface JobOutboundMessageStart extends JobOutboundMessageBase {
readonly kind: JobOutboundMessageKind.Start;
}
/**
* An output value is available.
*/
export interface JobOutboundMessageOutput<OutputT extends JsonValue>
extends JobOutboundMessageBase {
readonly kind: JobOutboundMessageKind.Output;
/**
* The message being outputted from the job.
*/
readonly value: OutputT;
}
/**
* Base interface for all job message related to channels.
*/
export interface JobOutboundMessageChannelBase extends JobOutboundMessageBase {
/**
* The name of the channel.
*/
readonly name: string;
}
/**
* A job wants to send a message to a channel. This can be marshaled, and the Job object
* has helpers to transform this into an observable. The context also can create RxJS subjects that
* marshall messages through a channel.
*/
export interface JobOutboundMessageChannelMessage extends JobOutboundMessageChannelBase {
readonly kind: JobOutboundMessageKind.ChannelMessage;
/**
* The message being sent to the channel.
*/
readonly message: JsonValue;
}
/**
* A job wants to send an error to one of its channel. This is the equivalent of throwing through
* an Observable. The side channel will not receive any more messages after this, and will not
* complete.
*/
export interface JobOutboundMessageChannelError extends JobOutboundMessageChannelBase {
readonly kind: JobOutboundMessageKind.ChannelError;
/**
* The error message being sent to the channel.
*/
readonly error: JsonValue;
}
/**
* A job wants to create a new channel.
*/
export interface JobOutboundMessageChannelCreate extends JobOutboundMessageChannelBase {
readonly kind: JobOutboundMessageKind.ChannelCreate;
}
/**
* A job wants to close the channel, as completed. This is done automatically when the job ends,
* or can be done from the job to close it. A closed channel might be reopened, but the user
* need to recall getChannel().
*/
export interface JobOutboundMessageChannelComplete extends JobOutboundMessageChannelBase {
readonly kind: JobOutboundMessageKind.ChannelComplete;
}
/**
* OnEnd of the job run.
*/
export interface JobOutboundMessageEnd extends JobOutboundMessageBase {
readonly kind: JobOutboundMessageKind.End;
}
/**
* A pong response from a ping input. The id is the same as the one passed in.
*/
export interface JobOutboundMessagePong extends JobOutboundMessageBase {
readonly kind: JobOutboundMessageKind.Pong;
/**
* The ID that was passed in the `Ping` messages.
*/
readonly id: number;
}
/**
* Generic message type.
*/
export type JobOutboundMessage<OutputT extends JsonValue> =
| JobOutboundMessageOnReady
| JobOutboundMessageStart
| JobOutboundMessageOutput<OutputT>
| JobOutboundMessageChannelCreate
| JobOutboundMessageChannelMessage
| JobOutboundMessageChannelError
| JobOutboundMessageChannelComplete
| JobOutboundMessageEnd
| JobOutboundMessagePong;
/**
* The state of a job. These are changed as the job reports a new state through its messages.
*/
export enum JobState {
/**
* The job was queued and is waiting to start.
*/
Queued = 'queued',
/**
* The job description was found, its dependencies (see "Synchronizing and Dependencies")
* are done running, and the job's argument is validated and the job's code will be executed.
*/
Ready = 'ready',
/**
* The job has been started. The job implementation is expected to send this as soon as its
* work is starting.
*/
Started = 'started',
/**
* The job has ended and is done running.
*/
Ended = 'ended',
/**
* An error occured and the job stopped because of internal state.
*/
Errored = 'errored',
}
/**
* A Job instance, returned from scheduling a job. A Job instance is _not_ serializable.
*/
export interface Job<
ArgumentT extends JsonValue = JsonValue,
InputT extends JsonValue = JsonValue,
OutputT extends JsonValue = JsonValue,
> {
/**
* Description of the job. Resolving the job's description can be done asynchronously, so this
* is an observable that will resolve when it's ready.
*/
readonly description: Observable<JobDescription>;
/**
* Argument sent when scheduling the job. This is a copy of the argument.
*/
readonly argument: ArgumentT;
/**
* The input to the job. This goes through the input channel as messages.
*/
readonly input: Observer<InputT>;
/**
* Outputs of this job.
*/
readonly output: Observable<OutputT>;
/**
* The current state of the job.
*/
readonly state: JobState;
/**
* Get a channel that validates against the schema. Messages will be filtered by the schema.
* @param name The name of the channel.
* @param schema A schema to use to validate messages.
*/
getChannel<T extends JsonValue>(name: string, schema?: schema.JsonSchema): Observable<T>;
/**
* Pings the job and wait for the resulting Pong before completing.
*/
ping(): Observable<never>;
/**
* Stops the job from running. This is different than unsubscribing from the output as in it
* sends the JobInboundMessageKind.Stop raw input to the job.
*/
stop(): void;
/**
* The JobInboundMessage messages TO the job.
*/
readonly inboundBus: Observer<JobInboundMessage<InputT>>;
/**
* The JobOutboundMessage FROM the job.
*/
readonly outboundBus: Observable<JobOutboundMessage<OutputT>>;
}
/**
* Options for scheduling jobs.
*/
export interface ScheduleJobOptions {
/**
* Jobs that need to finish before scheduling this job. These dependencies will be passed
* to the job itself in its context.
*/
dependencies?: Job | Job[];
}
export interface Registry<
MinimumArgumentValueT extends JsonValue = JsonValue,
MinimumInputValueT extends JsonValue = JsonValue,
MinimumOutputValueT extends JsonValue = JsonValue,
> {
/**
* Get a job handler.
* @param name The name of the job to get a handler from.
*/
get<A extends MinimumArgumentValueT, I extends MinimumInputValueT, O extends MinimumOutputValueT>(
name: JobName,
): Observable<JobHandler<A, I, O> | null>;
}
/**
* An interface that can schedule jobs.
*/
export interface Scheduler<
MinimumArgumentValueT extends JsonValue = JsonValue,
MinimumInputValueT extends JsonValue = JsonValue,
MinimumOutputValueT extends JsonValue = JsonValue,
> {
/**
* Get a job description for a named job.
*
* @param name The name of the job.
* @returns A description, or null if no description is available for this job.
*/
getDescription(name: JobName): Observable<JobDescription | null>;
/**
* Returns true if the job name has been registered.
* @param name The name of the job.
* @returns True if the job exists, false otherwise.
*/
has(name: JobName): Observable<boolean>;
/**
* Pause the scheduler, temporary queueing _new_ jobs. Returns a resume function that should be
* used to resume execution. If multiple `pause()` were called, all their resume functions must
* be called before the Scheduler actually starts new jobs. Additional calls to the same resume
* function will have no effect.
*
* Jobs already running are NOT paused. This is pausing the scheduler only.
*
* @returns A function that can be run to resume the scheduler. If multiple `pause()` calls
* were made, all their return function must be called (in any order) before the
* scheduler can resume.
*/
pause(): () => void;
/**
* Schedule a job to be run, using its name.
* @param name The name of job to be run.
* @param argument The argument to send to the job when starting it.
* @param options Scheduling options.
* @returns The job being run.
*/
schedule<
A extends MinimumArgumentValueT,
I extends MinimumInputValueT,
O extends MinimumOutputValueT,
>(
name: JobName,
argument: A,
options?: ScheduleJobOptions,
): Job<A, I, O>;
}
export function isJobHandler<A extends JsonValue, I extends JsonValue, O extends JsonValue>(
value: unknown,
): value is JobHandler<A, I, O> {
const job = value as JobHandler<A, I, O>;
return (
typeof job == 'function' && typeof job.jobDescription == 'object' && job.jobDescription !== null
);
}