system.d.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * ## Thinking in Systems
  3. * systems are a stateful **data-processing machines** which accept input through **input streams**,
  4. * init and maintain state in **depots** and, in certain conditions, emit values to subscriptions through **output streams**.
  5. * Systems can specify other systems as dependencies, and can act as singletons in the resulting dependency tree.
  6. *
  7. * ### Depots
  8. *
  9. * The first, and probably the most critical part to understand are **the depots**
  10. * mostly because they are somewhat implicit.
  11. * Unlike other state management paradigms, the depots are not kept in a single data structure.
  12. * Insted, depots are defined and maintained as stateful streams, stateful transfomers
  13. * like [[combineLatest]] or stateful operators like[ []withLatestFrom] or [[scan]].
  14. *
  15. * **Depots persist values over time**.
  16. * If it was not for them, the system had to re-receive its entire input state simultaneously in order to calculate the values for its output stream.
  17. *
  18. * Of course, strictly speaking, it is possible to implement a pure, stateless system as a form of a complex map/reduce. urx would not mind that ;).
  19. *
  20. * ### Input Streams
  21. *
  22. * The system receives updates from the rest of the world through values published in its input streams.
  23. * The streams used can be either stateless (acting as means to send **signals**) or stateful, where the initial stream state acts as the default value for that system parameter.
  24. *
  25. * The effects of the input streams are up to the system data-processing logic. It can change its depots' state, and/or emit values through its output streams.
  26. *
  27. * ### Data Processing
  28. *
  29. * The actual system behavior is exclusively implemented by **applying transformers and operators** to the input streams, producing the respective output streams.
  30. * In the final state the system streams are organized in a directed graph, where incoming data is routed through certain edges and nodes.
  31. * Simple systems like the one in [urx by example](https://urx.virtuoso.dev/docs/urx-by-example) can use a straightforward single-step transformation (in this case, `combineLatest` and `map`),
  32. * while complex ones can introduce multiple intermediate streams to model their logic.
  33. *
  34. * ### Output Streams
  35. *
  36. * The system publishes updates to its clients (other systems or an UI bound to it) by publishing data in its output streams.
  37. * State-reflecting output streams, like `sum` in the [urx by example](https://urx.virtuoso.dev/docs/urx-by-example) should use stateful streams as output streams.
  38. * Signal-like output should use regular, stateless streams. In general, stateless input streams tend to have a symmetrical stateless streams, and the opposite.
  39. *
  40. * @packageDocumentation
  41. */
  42. import { Emitter } from './actions';
  43. /**
  44. * Systems are a dictionaries of streams. a [[SystemConstructor]] should return a System.
  45. */
  46. export interface System {
  47. [key: string]: Emitter<any>;
  48. }
  49. /**
  50. * a SystemSpec is the result from a [[system]] call. To obtain the [[System]], pass the spec to [[init]].
  51. */
  52. export interface SystemSpec<SS extends SystemSpecs, C extends SystemConstructor<SS>> {
  53. id: string;
  54. constructor: C;
  55. dependencies: SS;
  56. singleton: boolean;
  57. }
  58. /** @internal **/
  59. export declare type AnySystemSpec = SystemSpec<any, any>;
  60. /** @internal **/
  61. export declare type SystemSpecs = AnySystemSpec[];
  62. /** @internal **/
  63. export declare type SR<E extends AnySystemSpec, R extends System = ReturnType<E['constructor']>> = R;
  64. /** @internal **/
  65. export declare type SpecResults<SS extends SystemSpecs, L = SS['length']> = L extends 0 ? [] : L extends 1 ? [SR<SS[0]>] : L extends 2 ? [SR<SS[0]>, SR<SS[1]>] : L extends 3 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>] : L extends 4 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>] : L extends 5 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>] : L extends 6 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>, SR<SS[5]>] : L extends 7 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>, SR<SS[5]>, SR<SS[6]>] : L extends 8 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>, SR<SS[5]>, SR<SS[6]>, SR<SS[7]>] : L extends 9 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>, SR<SS[5]>, SR<SS[6]>, SR<SS[7]>, SR<SS[8]>] : L extends 10 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>, SR<SS[5]>, SR<SS[6]>, SR<SS[7]>, SR<SS[8]>, SR<SS[9]>] : L extends 11 ? [SR<SS[0]>, SR<SS[1]>, SR<SS[2]>, SR<SS[3]>, SR<SS[4]>, SR<SS[5]>, SR<SS[6]>, SR<SS[7]>, SR<SS[8]>, SR<SS[9]>, SR<SS[10]>] : never;
  66. /**
  67. * The system constructor is a function which initializes and connects streams and returns them as a [[System]].
  68. * If the [[system]] call specifies system dependencies, the constructor receives the dependencies as an array argument.
  69. */
  70. export declare type SystemConstructor<D extends SystemSpecs> = (dependencies: SpecResults<D>) => System;
  71. /**
  72. * `system` defines a specification of a system - its constructor, dependencies and if it should act as a singleton in a system dependency tree.
  73. * When called, system returns a [[SystemSpec]], which is then initialized along with its dependencies by passing it to [[init]].
  74. *
  75. * ```ts
  76. * @import { subscribe, publish, system, init, tup, connect, map, pipe } from 'urx'
  77. *
  78. * // a simple system with two streams
  79. * const sys1 = system(() => {
  80. * const a = stream<number>()
  81. * const b = stream<number>()
  82. *
  83. * connect(pipe(a, map(value => value * 2)), b)
  84. * return { a, b }
  85. * })
  86. *
  87. * // a second system which depends on the streams from the first one
  88. * const sys2 = system(([ {a, b} ]) => {
  89. * const c = stream<number>()
  90. * connect(pipe(b, map(value => value * 2)), c)
  91. * // re-export the `a` stream, keep `b` internal
  92. * return { a, c }
  93. * }, tup(sys1))
  94. *
  95. * // init will recursively initialize sys2 dependencies, in this case sys1
  96. * const { a, c } = init(sys2)
  97. * subscribe(c, c => console.log(`Value multiplied by 4`, c))
  98. * publish(a, 2)
  99. * ```
  100. *
  101. * #### Singletons in Dependency Tree
  102. *
  103. * By default, systems will be initialized only once if encountered multiple times in the dependency tree.
  104. * In the below dependency system tree, systems `b` and `c` will receive the same stream instances from system `a` when system `d` is initialized.
  105. * ```txt
  106. * a
  107. * / \
  108. * b c
  109. * \ /
  110. * d
  111. * ```
  112. * If `a` gets `{singleton: false}` as a last argument, `init` creates two separate instances - one for `b` and one for `c`.
  113. *
  114. * @param constructor the system constructor function. Initialize and connect the streams in its body.
  115. *
  116. * @param dependencies the system dependencies, which the constructor will receive as arguments.
  117. * Use the [[tup]] utility **For TypeScript type inference to work correctly**.
  118. * ```ts
  119. * const sys3 = system(() => { ... }, tup(sys2, sys1))
  120. * ```
  121. * @param __namedParameters Options
  122. * @param singleton determines if the system will act as a singleton in a system dependency tree. `true` by default.
  123. */
  124. export declare function system<F extends SystemConstructor<D>, D extends SystemSpecs>(constructor: F, dependencies?: D, { singleton }?: {
  125. singleton: boolean;
  126. }): SystemSpec<D, F>;
  127. /**
  128. * Initializes a [[SystemSpec]] by recursively initializing its dependencies.
  129. *
  130. * ```ts
  131. * // a simple system with two streams
  132. * const sys1 = system(() => {
  133. * const a = stream<number>()
  134. * const b = stream<number>()
  135. *
  136. * connect(pipe(a, map(value => value * 2)), b)
  137. * return { a, b }
  138. * })
  139. *
  140. * const { a, b } = init(sys1)
  141. * subscribe(b, b => console.log(b))
  142. * publish(a, 2)
  143. * ```
  144. *
  145. * @returns the [[System]] constructed by the spec constructor.
  146. * @param systemSpec the system spec to initialize.
  147. */
  148. export declare function init<SS extends AnySystemSpec>(systemSpec: SS): SR<SS>;