@angular-devkit/core 7.2.0-beta.1 → 7.2.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.
Files changed (48) hide show
  1. package/node/_golden-api.d.ts +12 -0
  2. package/node/_golden-api.js +24 -0
  3. package/node/experimental/index.d.ts +9 -0
  4. package/node/experimental/index.js +12 -0
  5. package/node/experimental/job-registry.d.ts +22 -0
  6. package/node/experimental/job-registry.js +70 -0
  7. package/node/fs.d.ts +2 -4
  8. package/node/fs.js +25 -28
  9. package/node/index.d.ts +3 -1
  10. package/node/index.js +5 -2
  11. package/node/resolve.js +4 -4
  12. package/package.json +2 -2
  13. package/src/exception/exception.d.ts +1 -1
  14. package/src/exception/exception.js +2 -2
  15. package/src/experimental/jobs/README.md +495 -0
  16. package/src/experimental/jobs/api.d.ts +345 -0
  17. package/src/experimental/jobs/api.js +68 -0
  18. package/src/experimental/jobs/architecture.md +257 -0
  19. package/src/experimental/jobs/create-job-handler.d.ts +50 -0
  20. package/src/experimental/jobs/create-job-handler.js +145 -0
  21. package/src/experimental/jobs/dispatcher.d.ts +32 -0
  22. package/src/experimental/jobs/dispatcher.js +42 -0
  23. package/src/experimental/jobs/exception.d.ts +15 -0
  24. package/src/experimental/jobs/exception.js +23 -0
  25. package/src/experimental/jobs/index.d.ts +14 -0
  26. package/src/experimental/jobs/index.js +20 -0
  27. package/src/experimental/jobs/simple-registry.d.ts +44 -0
  28. package/src/experimental/jobs/simple-registry.js +74 -0
  29. package/src/experimental/jobs/simple-scheduler.d.ts +74 -0
  30. package/src/experimental/jobs/simple-scheduler.js +367 -0
  31. package/src/experimental/jobs/strategy.d.ts +15 -0
  32. package/src/experimental/jobs/strategy.js +55 -0
  33. package/src/experimental.d.ts +2 -1
  34. package/src/experimental.js +3 -1
  35. package/src/json/schema/index.d.ts +1 -0
  36. package/src/json/schema/index.js +2 -1
  37. package/src/json/schema/registry.d.ts +2 -1
  38. package/src/json/schema/registry.js +10 -1
  39. package/src/json/schema/schema.d.ts +15 -0
  40. package/src/json/schema/schema.js +52 -0
  41. package/src/json/schema/utility.d.ts +2 -9
  42. package/src/json/schema/utility.js +1 -1
  43. package/src/json/schema/visitor.d.ts +2 -1
  44. package/src/json/schema/visitor.js +5 -1
  45. package/src/logger/logger.d.ts +1 -0
  46. package/src/logger/logger.js +4 -1
  47. package/src/utils/index.d.ts +9 -0
  48. package/src/utils/index.js +1 -1
@@ -0,0 +1,495 @@
1
+ # Description
2
+
3
+ Jobs is the Angular DevKit subsystem for scheduling and running generic functions with clearly
4
+ typed inputs and outputs. A `Job` instance is a function associated with metadata. You can
5
+ schedule a job, synchronize it with other jobs, and use it to schedule other jobs.
6
+
7
+ The whole API is serializable, allowing you to use a Node Stream or message channel to
8
+ communicate between the job and the job scheduler.
9
+
10
+ Jobs are lazy, cold, and guaranteed to execute exactly once when scheduled. Subscribing to a job
11
+ returns messages from the point where the job is at.
12
+
13
+ ## Argument, Input, Output and Channels
14
+ A job receives a single argument when scheduled and can also listen to an input channel. It can
15
+ emit multiple outputs, and can also provide multiple output channels that emit asynchronous JSON
16
+ messages, which can be typed.
17
+
18
+ The I/O model is like that of an executable, where the argument corresponds to arguments on the
19
+ command line, the input channel to STDIN, the output channel to STDOUT, and the channels
20
+ would be additional output streams.
21
+
22
+ In addition, a `Job` has a logging channel that can be used to log messages to the user. The
23
+ code that schedules the job must listen for or forward these messages. You can think of those
24
+ messages as STDERR.
25
+
26
+ ## LifeCycle
27
+ A `Job` goes through multiple LifeCycle messages before its completion;
28
+ 1. `JobState.Queued`. The job was queued and is waiting. This is the default state from the
29
+ scheduler.
30
+ 1. `JobState.Ready`. The job's dependencies (see
31
+ ["Synchronizing and Dependencies"](#Dependencies)) are done running, the argument is
32
+ validated, and the job is ready to execute.
33
+ 1. `JobState.Started`. The argument has been validated, the job has been called and is running.
34
+ This is handled by the job itself (or `createJobHandler()`).
35
+ 1. `JobState.Ended`. The job has ended and is done running. This is handled by the job itself (or
36
+ `createJobHandler()`).
37
+ 1. `JobState.Errored`. A unrecoverable error happened.
38
+
39
+ Each state (except `Queued`) corresponds to a `JobOutboundMessage` on the `outboundBus` observable
40
+ that triggers the state change. The `Scheduler` emits the `Ready` and `Errored` messages; the job
41
+ implementation should not emit them, and if it does they are filtered out. You can listen for
42
+ these messages or use the corresponding state member.
43
+
44
+ The job implementation should emit the `Start` and `End` messages when it is starting the job logic
45
+ itself. Only the first `Start` and `End` messages will be forwarded. Any more will be filtered out.
46
+
47
+ The `Queued` state is set as the job is scheduled, so there is no need to listen for the message.
48
+
49
+ ## `Job<OutputType>` Object
50
+ The `Job` object that is returned when you schedule a job provides access to the job's status and
51
+ utilities for tracking and modifying the job.
52
+
53
+ 1. `id`. A unique symbol that can be used as a Map key.
54
+ 1. `description`. The description of the job from the scheduler. See `JobDescription` object.
55
+ 1. `argument`. The argument value that was used to start the job.
56
+ 1. `input`. An `Observer` that can be used to send validated inputs to the job itself.
57
+ 1. `output`. An `Observable<OutputType>` that filters out messages to get only the returned output
58
+ of a job.
59
+ 1. `promise`. A promise that waits for the last output of a job. Returns the last value outputted
60
+ (or no value if there's no last value).
61
+ 1. `state`. The current state of the job (see `LifeCycle`).
62
+ 1. `channels`. A map of side channels the user can listen to as `Observable`.
63
+ 1. `ping()`. A function that can be used to ping the job, receiving a `Promise` for when the ping
64
+ is answered.
65
+ 1. `stop()`. Sends a `stop` input to the job, which suggests to stop the job. The job itself can
66
+ choose to ignore this message.
67
+ 1. `inboundBus`. The raw input `Observer<JobInboundMessage>`. This can be used to send messages to
68
+ the `context.inboundBus` observable in the job. These are `JobInboundMessage` messages. See
69
+ ["Communicating With Jobs"](#Communicating).
70
+ 1. `outboundBus`. The raw output `Observable<JobOutput>`. This can be used to listen to messages
71
+ from the job. See ["Communicating With Jobs"](#Communicating).
72
+
73
+ ## `JobHandlerContext<I, O>` Object
74
+ The `JobHandlerContext<>` is passed to the job handler code in addition to its argument. The
75
+ context contains the following members:
76
+
77
+ 1. `description`. The description of the job. Its name and schemas.
78
+ 1. `scheduler`. A `Scheduler<>` instance that can be used to create additional jobs.
79
+ 1. `dependencies`. A generic list of other job instances that were run as dependencies when
80
+ scheduling this job. Their `id` is not guaranteed to match the `id` of the `Job<>` instance
81
+ itself (those `Job<>`s might just be proxies). The state of those `Job<>` is guaranteed to be
82
+ `JobState.Ended`, as `JobState.Errored` would have prevented this handler from running.
83
+ 1. `inboundBus`. The raw input observable, complement of the `inboundBus` observer from the `Job<>`.
84
+
85
+ # Examples
86
+
87
+ An example of a job that adds all input together and return the output value. We use a
88
+ simple synchronous job registry and a simple job scheduler.
89
+
90
+ ```typescript
91
+ import { jobs } from '@angular-devkit/core';
92
+
93
+ const add = jobs.createJobHandle<number[], number>(
94
+ input => input.reduce((total, curr) => total + curr, 0),
95
+ );
96
+
97
+ // Register the job in a SimpleJobRegistry. Different registries have different API.
98
+ const registry = new jobs.SimpleJobRegistry();
99
+ const scheduler = new jobs.SimpleScheduler(registry);
100
+ registry.register(add, {
101
+ name: 'add',
102
+ input: { type: 'array', items: { type: 'number' } },
103
+ output: { type: 'number' },
104
+ });
105
+
106
+ scheduler.schedule('add', [1, 2, 3, 4]).promise
107
+ .then(output => console.log('1 + 2 + 3 + 4 is ' + output));
108
+ ```
109
+
110
+ # Creating Jobs
111
+
112
+ A job is at its core a function with a description object attached to it. The description object
113
+ stores the JSON schemas used to validate the types of the argument passed in, the input and
114
+ output values. By default, a job accepts and can output any JSON object.
115
+
116
+ ```typescript
117
+ import { Observable } from 'rxjs';
118
+ import { jobs } from '@angular-devkit/core';
119
+
120
+ const argument = {
121
+ type: 'array', items: { type: 'number' },
122
+ };
123
+ const output = {
124
+ type: 'number',
125
+ };
126
+
127
+ export function add(argument: number[]): Observable<jobs.JobOutboundMessage<number>> {
128
+ return new Observable(o => {
129
+ o.next({ kind: jobs.JobOutboundMessageKind.Start });
130
+ o.next({
131
+ kind: jobs.JobOutboundMessageKind.Output,
132
+ output: argument.reduce((total, curr) => total + curr, 0),
133
+ });
134
+ o.next({ kind: jobs.JobOutboundMessageKind.End });
135
+ o.complete();
136
+ });
137
+ }
138
+
139
+ // Add a property to `add` to make it officially a JobHandler. The Job system does not recognize
140
+ // any function as a JobHandler.
141
+ add.jobDescription = {
142
+ argument: argument,
143
+ output: output,
144
+ };
145
+
146
+ // Call the job with an array as argument, and log its output.
147
+ declare const scheduler: jobs.Scheduler;
148
+ scheduler.schedule('add', [1, 2, 3, 4])
149
+ .output.subscribe(x => console.log(x)); // Will output 10.
150
+ ```
151
+
152
+ This is a lot of boilerplate, so we made some helpers to improve readability and manage argument,
153
+ input and output automatically:
154
+
155
+ ```typescript
156
+ // Add is a JobHandler function, like the above.
157
+ export const add = jobs.createJobHandler<number[], number>(
158
+ argument => argument.reduce((total, curr) => total + curr, 0),
159
+ );
160
+
161
+ // Schedule like above.
162
+ ```
163
+
164
+ You can also return a Promise or an Observable, as jobs are asynchronous. This helper will set
165
+ start and end messages appropriately, and will pass in a logger. It will also manage channels
166
+ automatically (see below).
167
+
168
+ A more complex job can be declared like this:
169
+
170
+ ```typescript
171
+ import { Observable } from 'rxjs';
172
+ import { jobs } from '@angular-devkit/core';
173
+
174
+ // Show progress with each count in a separate output channel. Output "more" in a channel.
175
+ export const count = jobs.createJobHandler<number, number>(
176
+ // Receive a context that contains additional methods to create channels.
177
+ (argument: number, { createChannel }) => new Observable<number>(o => {
178
+ const side = createChannel('side', { type: 'string', const: 'more' });
179
+ const progress = createChannel('progress', { type: 'number' });
180
+ let i = 0;
181
+ function doCount() {
182
+ o.next(i++);
183
+ progress.next(i / argument);
184
+ side.next('more');
185
+
186
+ if (i < argument) {
187
+ setTimeout(doCount, 100);
188
+ } else {
189
+ o.complete();
190
+ }
191
+ }
192
+ setTimeout(doCount, 100);
193
+ }),
194
+ {
195
+ argument: { type: 'number' },
196
+ output: { type: 'number' },
197
+ },
198
+ );
199
+
200
+ // Get a hold of a scheduler that refers to the job above.
201
+ declare const scheduler: jobs.Scheduler;
202
+
203
+ const job = scheduler.schedule('count', 0);
204
+ job.getChannel('side').subscribe(x => console.log(x));
205
+ // You can type a channel too. Messages will be filtered out.
206
+ job.getChannel<number>('progress', { type: 'number' }).subscribe(x => console.log(x));
207
+ ```
208
+
209
+ ## <a name="Communicating"></a>Communicating With Jobs
210
+ Jobs can be started and updated in a separate process or thread, and as such communication with a
211
+ job should avoid using global objects (which might not be shared). The jobs API and schedulers
212
+ provide 2 communication streams (one for input and the other for output), named `inboundBus` and
213
+ `outboundBus`.
214
+
215
+ ### Raw Input Stream
216
+ The `schedule()` function returns a `Job<>` interface that contains a `inboundBus` member of type
217
+ `Observer<JobInboundMessage>`. All messages sent _to_ the job goes through this stream. The `kind`
218
+ member of the `JobInboundMessage` interface dictates what kind of message it is sending:
219
+
220
+ 1. `JobInboundMessageKind.Ping`. A simple message that should be answered with
221
+ `JobOutboundMessageKind.Pong` when the job is responsive. The `id` field of the message should
222
+ be used when returning `Pong`.
223
+ 1. `JobInboundMessageKind.Stop`. The job should be stopped. This is used when
224
+ cancelling/unsubscribing from the `output` (or by calling `stop()`). Any inputs or outputs
225
+ after this message will be ignored.
226
+ 1. `JobInboundMessageKind.Input` is used when sending inputs to a job. These correspond to the
227
+ `next` methods of an `Observer` and are reported to the job through its `context.input`
228
+ Observable. There is no way to communicate an error to the job.
229
+
230
+ Using the `createJobHandler()` helper, all those messages are automatically handled by the
231
+ boilerplate code. If you need direct access to raw inputs, you should subscribe to the
232
+ `context.inboundBus` Observable.
233
+
234
+ ### Raw Output Stream
235
+ The `Job<>` interface also contains a `outboundBus` member (of type
236
+ `Observable<JobOutboundMessage<O>>` where `O` is the typed output of the job) which is the output
237
+ complement of `inboundBus`. All messages sent _from_ the job goes through this stream. The `kind`
238
+ member of the `JobOutboundMessage<O>` interface dictates what kind of message it is sending:
239
+
240
+ 1. `JobOutboundMessageKind.Create`. The `Job<>` was created, its dependencies are done, and the
241
+ library is validating Argument and calling the internal job code.
242
+ 1. `JobOutboundMessageKind.Start`. The job code itself should send that message when started.
243
+ `createJobHandler()` will do it automatically.
244
+ 1. `JobOutboundMessageKind.End`. The job has ended. This is done by the job itself and should always
245
+ be sent when completed. The scheduler will listen to this message to set the state and unblock
246
+ dependent jobs. `createJobHandler()` automatically send this message.
247
+ 1. `JobOutboundMessageKind.Pong`. The job should answer a `JobInboundMessageKind.Ping` message with
248
+ this. Automatically done by `createJobHandler()`.
249
+ 1. `JobOutboundMessageKind.Log`. A logging message (side effect that should be shown to the user).
250
+ 1. `JobOutboundMessageKind.Output`. An `Output` has been generated by the job.
251
+ 1. `JobOutboundMessageKind.ChannelMessage`, `JobOutboundMessageKind.ChannelError` and
252
+ `JobOutboundMessageKind.ChannelComplete` are used for output channels. These correspond to the
253
+ `next`, `error` and `complete` methods of an `Observer` and are available to the callee through
254
+ the `job.channels` map of Observable.
255
+
256
+ Those messages can be accessed directly through the `job.outboundBus` member. The job itself should
257
+ return an `Observable<JobOutboundMessage<O>>`. The `createJobHandler()` helper handles most of use
258
+ cases of this and makes it easier for jobs to handle this.
259
+
260
+ ## Job Dispatchers
261
+ Dispatchers are a helper that redirect to different jobs given conditions. To create a job
262
+ dispatcher, use the `createDispatcher()` function:
263
+
264
+ ```typescript
265
+ import { jobs } from '@angular-devkit/core';
266
+
267
+ // A dispatcher that installs node modules given a user's preference.
268
+ const dispatcher = jobs.createDispatcher({
269
+ name: 'node-install',
270
+ argument: { properties: { moduleName: { type: 'string' } } },
271
+ output: { type: 'boolean' },
272
+ });
273
+
274
+ const npmInstall = jobs.createJobHandler(/* ... */, { name: 'npm-install' });
275
+ const yarnInstall = jobs.createJobHandler(/* ... */, { name: 'yarn-install' });
276
+ const pnpmInstall = jobs.createJobHandler(/* ... */, { name: 'pnpm-install' });
277
+
278
+ declare const registry: jobs.SimpleJobRegistry;
279
+ registry.register(dispatcher);
280
+ registry.register(npmInstall);
281
+ registry.register(yarnInstall);
282
+ registry.register(pnpmInstall);
283
+
284
+ // Default to npm.
285
+ dispatcher.setDefaultDelegate(npmInstall.name);
286
+ // If the user is asking for yarn over npm, uses it.
287
+ dispatcher.addConditionalDelegate(() => userWantsYarn, yarnInstall.name);
288
+ ```
289
+
290
+ ## Execution Strategy
291
+ Jobs are always run in parallel and will always start, but many helper functions are provided
292
+ when creating a job to help you control the execution strategy;
293
+
294
+ 1. `serialize()`. Multiple runs of this job will be queued with each others.
295
+ 1. `memoize(replayMessages = false)` will create a job, or reuse the same job when inputs are
296
+ matching. If the inputs don't match, a new job will be started and its outputs will be stored.
297
+
298
+ These strategies can be used when creating the job:
299
+
300
+ ```typescript
301
+ // Same input and output as above.
302
+
303
+ export const add = jobs.strategy.memoize()(
304
+ jobs.createJobHandler<number[], number>(
305
+ argument => argument.reduce((total, curr) => total + curr, 0),
306
+ ),
307
+ );
308
+ ```
309
+
310
+ Strategies can be reused to synchronize between jobs. For example, given jobs `jobA` and `jobB`,
311
+ you can reuse the strategy to serialize both jobs together;
312
+
313
+ ```typescript
314
+ const strategy = jobs.strategy.serialize();
315
+ const jobA = strategy(jobs.createJobHandler(...));
316
+ const jobB = strategy(jobs.createJobHandler(...));
317
+ ```
318
+
319
+ Even further, we can have package A and package B run in serialization, and B and C also be
320
+ serialized. Running A and C will run in parallel, while running B will wait for both A and C
321
+ to finish.
322
+
323
+ ```typescript
324
+ const strategy1 = jobs.strategy.serialize();
325
+ const strategy2 = jobs.strategy.serialize();
326
+ const jobA = strategy1(jobs.createJobHandler(...));
327
+ const jobB = strategy1(strategy2(jobs.createJobHandler(...)));
328
+ const jobC = strategy2(jobs.createJobHandler(...));
329
+ ```
330
+
331
+ # Scheduling Jobs
332
+ Jobs can be scheduled using a `Scheduler` interface, which contains a `schedule()` method:
333
+
334
+ ```typescript
335
+ interface Scheduler {
336
+ /**
337
+ * Schedule a job to be run, using its name.
338
+ * @param name The name of job to be run.
339
+ * @param argument The argument to send to the job when starting it.
340
+ * @param options Scheduling options.
341
+ * @returns The Job being run.
342
+ */
343
+ schedule<I extends MinimumInputValueT, O extends MinimumOutputValueT>(
344
+ name: JobName,
345
+ argument: I,
346
+ options?: ScheduleJobOptions,
347
+ ): Job<JsonValue, O>;
348
+ }
349
+ ```
350
+
351
+ The scheduler also has a `getDescription()` method to get a `JobDescription` object for a certain
352
+ name; that description contains schemas for the argument, input, output, and other channels:
353
+
354
+ ```typescript
355
+ interface Scheduler {
356
+ /**
357
+ * Get a job description for a named job.
358
+ *
359
+ * @param name The name of the job.
360
+ * @returns A description, or null if the job cannot be scheduled.
361
+ */
362
+ getDescription(name: JobName): JobDescription | null;
363
+
364
+ /**
365
+ * Returns true if the job name has been registered.
366
+ * @param name The name of the job.
367
+ * @returns True if the job exists, false otherwise.
368
+ */
369
+ has(name: JobName): boolean;
370
+ }
371
+ ```
372
+
373
+ Finally, the scheduler interface has a `pause()` method to stop scheduling. This will queue all
374
+ jobs and wait for the unpause function to be called before unblocking all the jobs scheduled.
375
+ This does not affect already running jobs.
376
+
377
+ ```typescript
378
+ interface Scheduler {
379
+ /**
380
+ * Pause the scheduler, temporary queueing _new_ jobs. Returns a resume function that should be
381
+ * used to resume execution. If multiple `pause()` were called, all their resume functions must
382
+ * be called before the Scheduler actually starts new jobs. Additional calls to the same resume
383
+ * function will have no effect.
384
+ *
385
+ * Jobs already running are NOT paused. This is pausing the scheduler only.
386
+ *
387
+ * @returns A function that can be run to resume the scheduler. If multiple `pause()` calls
388
+ * were made, all their return function must be called (in any order) before the
389
+ * scheduler can resume.
390
+ */
391
+ pause(): () => void;
392
+ }
393
+ ```
394
+
395
+ ## <a name="Dependencies"></a>Synchronizing and Dependencies
396
+ When scheduling jobs, it is often necessary to run jobs after certain other jobs are finished.
397
+ This is done through the `dependencies` options in the `schedule()` method.
398
+
399
+ These jobs will also be passed to the job being scheduled, through its context. This can be
400
+ useful if, for example, the output of those jobs are of a known type, or have known side channels.
401
+
402
+ An example of this would be a compiler that needs to know the output directory of other compilers
403
+ before it, in a tool chain.
404
+
405
+ ### Dependencies
406
+ When scheduling jobs, the user can add a `dependencies` field to the scheduling options. The
407
+ scheduler will wait for those dependencies to finish before running the job, and pass those jobs
408
+ in the context of the job.
409
+
410
+ ### Accessing Dependencies
411
+ Jobs are called with a `JobHandlerContext` as a second argument, which contains a
412
+ `dependencies: Job<JsonValue>[]` member which contains all dependencies that were used when
413
+ scheduling the job. Those aren't fully typed as they are determined by the user, and not the job
414
+ itself. They also can contain jobs that are not finished, and the job should use the `state`
415
+ member of the job itself before trying to access its content.
416
+
417
+ ### Scheduler Sub Jobs
418
+ The `JobHandlerContext` also contains a `scheduler` member which can be used to schedule jobs
419
+ using the same scheduler that was used for the job. This allows jobs to call other jobs
420
+ and wait for them to end.
421
+
422
+ ## Available Schedulers
423
+ The Core Angular DevKit library provides 2 implementations for the `Scheduler` interface:
424
+
425
+ ## SimpleJobRegistry
426
+ Available in the jobs namespace. A registry that accept job registration, and can also schedule
427
+ jobs.
428
+
429
+ ```typescript
430
+ import { jobs } from '@angular-devkit/core';
431
+
432
+ const add = jobs.createJobHandler<number[], number>(
433
+ argument => argument.reduce((total, curr) => total + curr, 0),
434
+ );
435
+
436
+ // Register the job in a SimpleJobRegistry. Different registries have different API.
437
+ const registry = new jobs.SimpleJobRegistry();
438
+ const scheduler = new SimpleJobScheduler(registry);
439
+
440
+ registry.register(add, {
441
+ name: 'add',
442
+ argument: { type: 'array', items: { type: 'number' } },
443
+ output: { type: 'number' },
444
+ });
445
+
446
+ scheduler.schedule('add', [1, 2, 3, 4]);
447
+ ```
448
+
449
+ ## NodeModuleJobRegistry
450
+ Available through `@angular-devkit/core/node`.
451
+
452
+ A scheduler that loads jobs using their node package names. These jobs need to use the
453
+ `createJobHandler()` helper and report their argument/input/output schemas that way.
454
+
455
+ ```typescript
456
+ declare const registry: NodeModuleJobRegistry;
457
+ const scheduler = new SimpleJobScheduler(registry);
458
+
459
+ scheduler.schedule('some-node-package#someExport', 'input');
460
+ ```
461
+
462
+ # Gotchas
463
+
464
+ 1. Deadlocking Dependencies
465
+ It is impossible to add dependencies to an already running job, but it is entirely possible to
466
+ get locked between jobs. Be aware of your own dependencies.
467
+
468
+ 1. Using `job.promise`
469
+ `job.promise` waits for the job to ends. Don't rely on it unless you know the job is not
470
+ watching and running for a long time. If you aren't sure, use
471
+ `job.output.pipe(first()).toPromise()` instead which will return the first next output,
472
+ regardless of whether the job watches and rerun or not.
473
+
474
+
475
+ # FAQ
476
+
477
+ 1. Laziness
478
+ A job is lazy until executed, but its messages will be replayed when resubscribed.
479
+
480
+ 1. Serialize Strategy vs Dependencies
481
+ Strategies are functions that transform the execution of a job, and can be used when
482
+ declaring the job, or registering it. Dependencies, on the other hand, are listed when
483
+ scheduling a job to order jobs during scheduling.
484
+
485
+ A job has no control over the way it's scheduled, and its dependencies. It can, however,
486
+ declare that it shouldn't run at the same time as itself. Alternatively, a user could
487
+ schedule a job twice and imply that the second run should wait for the first to finish. In
488
+ practice, this would be equivalent to having the job be serialized, but the important detail
489
+ is in _whom_ is defining the rules; using the `serialize()` strategy, the job implementation
490
+ is, while when using dependencies, the user is.
491
+
492
+ The user does not need to know how to job needs to synchronize with itself, and the job does
493
+ not need to know how it synchronizes with other jobs that it doesn't know about. That's part
494
+ of the strength of this system as every job can be developed in a vacuum, only caring about
495
+ its contracts (argument, input and output) and its own synchronization.