@quantform/core 0.7.0-beta.4 → 0.7.0-beta.40

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 (179) hide show
  1. package/dist/asset/asset.d.ts +3 -0
  2. package/dist/asset/asset.d.ts.map +1 -1
  3. package/dist/asset/asset.js +7 -1
  4. package/dist/cli/dev.d.ts.map +1 -1
  5. package/dist/cli/dev.js +3 -36
  6. package/dist/cli/internal/script.d.ts +8 -0
  7. package/dist/cli/internal/script.d.ts.map +1 -0
  8. package/dist/cli/internal/script.js +58 -0
  9. package/dist/cli/pull.d.ts.map +1 -1
  10. package/dist/cli/pull.js +4 -83
  11. package/dist/cli/replay.d.ts.map +1 -1
  12. package/dist/cli/replay.js +3 -35
  13. package/dist/cli/run.d.ts.map +1 -1
  14. package/dist/cli/run.js +3 -37
  15. package/dist/index.d.ts +5 -14
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5 -14
  18. package/dist/instrument/instrument.d.ts +3 -0
  19. package/dist/instrument/instrument.d.ts.map +1 -1
  20. package/dist/instrument/instrument.js +7 -1
  21. package/dist/make-test-module.d.ts +3 -2
  22. package/dist/make-test-module.d.ts.map +1 -1
  23. package/dist/make-test-module.js +13 -2
  24. package/dist/operators.d.ts +5 -0
  25. package/dist/operators.d.ts.map +1 -0
  26. package/dist/operators.js +16 -0
  27. package/dist/replay/index.d.ts +6 -4
  28. package/dist/replay/index.d.ts.map +1 -1
  29. package/dist/replay/index.js +6 -4
  30. package/dist/replay/replay-guard.d.ts +10 -0
  31. package/dist/replay/replay-guard.d.ts.map +1 -0
  32. package/dist/replay/replay-guard.js +8 -0
  33. package/dist/replay/replay.d.ts +10 -0
  34. package/dist/replay/replay.d.ts.map +1 -0
  35. package/dist/replay/replay.js +9 -0
  36. package/dist/replay/use-replay-breakpoint.d.ts +3 -0
  37. package/dist/replay/use-replay-breakpoint.d.ts.map +1 -0
  38. package/dist/replay/use-replay-breakpoint.js +22 -0
  39. package/dist/replay/use-replay-manager.d.ts +12 -0
  40. package/dist/replay/use-replay-manager.d.ts.map +1 -0
  41. package/dist/replay/use-replay-manager.js +88 -0
  42. package/dist/replay/use-replay-storage-buffer.d.ts +15 -0
  43. package/dist/replay/use-replay-storage-buffer.d.ts.map +1 -0
  44. package/dist/replay/use-replay-storage-buffer.js +50 -0
  45. package/dist/replay/use-replay-storage.d.ts +11 -8
  46. package/dist/replay/use-replay-storage.d.ts.map +1 -1
  47. package/dist/replay/use-replay-storage.js +34 -5
  48. package/dist/replay/use-replay-storage.spec.d.ts +2 -0
  49. package/dist/replay/use-replay-storage.spec.d.ts.map +1 -0
  50. package/dist/replay/use-replay-storage.spec.js +74 -0
  51. package/dist/replay/use-replay.d.ts +2 -2
  52. package/dist/replay/use-replay.d.ts.map +1 -1
  53. package/dist/replay/use-replay.js +8 -20
  54. package/dist/replay/use-replay.spec.js +125 -101
  55. package/dist/session/use-session-storage.js +2 -2
  56. package/dist/session/use-session.js +2 -2
  57. package/dist/storage/index.d.ts +1 -0
  58. package/dist/storage/index.d.ts.map +1 -1
  59. package/dist/storage/index.js +1 -0
  60. package/dist/storage/use-storage.d.ts.map +1 -1
  61. package/dist/storage/use-storage.js +2 -2
  62. package/dist/use-execution-mode.d.ts +6 -1
  63. package/dist/use-execution-mode.d.ts.map +1 -1
  64. package/dist/use-execution-mode.js +16 -8
  65. package/dist/use-logger.js +4 -4
  66. package/dist/use-memo.d.ts +0 -10
  67. package/dist/use-memo.d.ts.map +1 -1
  68. package/dist/use-memo.js +0 -10
  69. package/dist/use-timestamp.js +2 -2
  70. package/dist/when-socket.d.ts +8 -0
  71. package/dist/when-socket.d.ts.map +1 -0
  72. package/dist/{use-socket.js → when-socket.js} +6 -5
  73. package/dist/with-memo.d.ts +5 -0
  74. package/dist/with-memo.d.ts.map +1 -0
  75. package/dist/{use.js → with-memo.js} +5 -5
  76. package/dist/with-memo.spec.d.ts +2 -0
  77. package/dist/with-memo.spec.d.ts.map +1 -0
  78. package/dist/{use.spec.js → with-memo.spec.js} +3 -3
  79. package/dist/{use-request.d.ts → with-request.d.ts} +5 -4
  80. package/dist/with-request.d.ts.map +1 -0
  81. package/dist/with-request.js +69 -0
  82. package/package.json +1 -1
  83. package/src/asset/asset.ts +6 -0
  84. package/src/cli/dev.ts +4 -20
  85. package/src/cli/internal/script.ts +49 -0
  86. package/src/cli/pull.ts +6 -66
  87. package/src/cli/replay.ts +4 -16
  88. package/src/cli/run.ts +4 -18
  89. package/src/index.ts +5 -14
  90. package/src/instrument/instrument.ts +6 -0
  91. package/src/make-test-module.ts +24 -4
  92. package/src/operators.ts +18 -0
  93. package/src/replay/index.ts +6 -4
  94. package/src/replay/replay-guard.ts +11 -0
  95. package/src/replay/replay.ts +13 -0
  96. package/src/replay/use-replay-breakpoint.ts +29 -0
  97. package/src/replay/use-replay-manager.ts +106 -0
  98. package/src/replay/use-replay-storage-buffer.ts +43 -0
  99. package/src/replay/use-replay-storage.spec.ts +85 -0
  100. package/src/replay/use-replay-storage.ts +26 -5
  101. package/src/replay/use-replay.spec.ts +10 -3
  102. package/src/replay/use-replay.ts +11 -17
  103. package/src/session/use-session-storage.ts +2 -2
  104. package/src/session/use-session.ts +2 -2
  105. package/src/storage/index.ts +1 -0
  106. package/src/storage/use-storage.ts +2 -2
  107. package/src/use-execution-mode.ts +16 -8
  108. package/src/use-logger.ts +4 -4
  109. package/src/use-memo.ts +0 -10
  110. package/src/use-timestamp.ts +2 -2
  111. package/src/{use-socket.ts → when-socket.ts} +6 -4
  112. package/src/{use.spec.ts → with-memo.spec.ts} +3 -3
  113. package/src/{use.ts → with-memo.ts} +4 -4
  114. package/src/with-request.ts +83 -0
  115. package/dist/as-readonly.d.ts +0 -3
  116. package/dist/as-readonly.d.ts.map +0 -1
  117. package/dist/as-readonly.js +0 -8
  118. package/dist/defined.d.ts +0 -3
  119. package/dist/defined.d.ts.map +0 -1
  120. package/dist/defined.js +0 -8
  121. package/dist/exclude.d.ts +0 -3
  122. package/dist/exclude.d.ts.map +0 -1
  123. package/dist/exclude.js +0 -8
  124. package/dist/not-found.d.ts +0 -2
  125. package/dist/not-found.d.ts.map +0 -1
  126. package/dist/not-found.js +0 -4
  127. package/dist/replay/use-replay-coordinator.d.ts +0 -10
  128. package/dist/replay/use-replay-coordinator.d.ts.map +0 -1
  129. package/dist/replay/use-replay-coordinator.js +0 -119
  130. package/dist/replay/use-replay-reader.d.ts +0 -7
  131. package/dist/replay/use-replay-reader.d.ts.map +0 -1
  132. package/dist/replay/use-replay-reader.js +0 -32
  133. package/dist/replay/use-replay-reader.spec.d.ts +0 -2
  134. package/dist/replay/use-replay-reader.spec.d.ts.map +0 -1
  135. package/dist/replay/use-replay-reader.spec.js +0 -58
  136. package/dist/replay/use-replay-writer.d.ts +0 -6
  137. package/dist/replay/use-replay-writer.d.ts.map +0 -1
  138. package/dist/replay/use-replay-writer.js +0 -14
  139. package/dist/replay/use-replay-writer.spec.d.ts +0 -2
  140. package/dist/replay/use-replay-writer.spec.d.ts.map +0 -1
  141. package/dist/replay/use-replay-writer.spec.js +0 -53
  142. package/dist/replay/with-replay.d.ts +0 -4
  143. package/dist/replay/with-replay.d.ts.map +0 -1
  144. package/dist/replay/with-replay.js +0 -8
  145. package/dist/strat.d.ts +0 -7
  146. package/dist/strat.d.ts.map +0 -1
  147. package/dist/strat.js +0 -7
  148. package/dist/use-lock.d.ts +0 -9
  149. package/dist/use-lock.d.ts.map +0 -1
  150. package/dist/use-lock.js +0 -40
  151. package/dist/use-request.d.ts.map +0 -1
  152. package/dist/use-request.js +0 -27
  153. package/dist/use-socket.d.ts +0 -6
  154. package/dist/use-socket.d.ts.map +0 -1
  155. package/dist/use-state.d.ts +0 -4
  156. package/dist/use-state.d.ts.map +0 -1
  157. package/dist/use-state.js +0 -24
  158. package/dist/use-state.spec.d.ts +0 -2
  159. package/dist/use-state.spec.d.ts.map +0 -1
  160. package/dist/use-state.spec.js +0 -36
  161. package/dist/use.d.ts +0 -5
  162. package/dist/use.d.ts.map +0 -1
  163. package/dist/use.spec.d.ts +0 -2
  164. package/dist/use.spec.d.ts.map +0 -1
  165. package/src/as-readonly.ts +0 -5
  166. package/src/defined.ts +0 -6
  167. package/src/exclude.ts +0 -9
  168. package/src/not-found.ts +0 -1
  169. package/src/replay/use-replay-coordinator.ts +0 -142
  170. package/src/replay/use-replay-reader.spec.ts +0 -64
  171. package/src/replay/use-replay-reader.ts +0 -23
  172. package/src/replay/use-replay-writer.spec.ts +0 -56
  173. package/src/replay/use-replay-writer.ts +0 -17
  174. package/src/replay/with-replay.ts +0 -10
  175. package/src/strat.ts +0 -7
  176. package/src/use-lock.ts +0 -52
  177. package/src/use-request.ts +0 -47
  178. package/src/use-state.spec.ts +0 -31
  179. package/src/use-state.ts +0 -30
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.withRequest = exports.RequestNetworkError = void 0;
13
+ const node_crypto_1 = require("node:crypto");
14
+ const rxjs_1 = require("rxjs");
15
+ const undici_1 = require("undici");
16
+ const use_logger_1 = require("./use-logger");
17
+ const use_timestamp_1 = require("./use-timestamp");
18
+ class RequestNetworkError extends Error {
19
+ constructor(statusCode, json) {
20
+ super(`Request network error, received status code: ${statusCode}`);
21
+ this.statusCode = statusCode;
22
+ this.json = json;
23
+ }
24
+ }
25
+ exports.RequestNetworkError = RequestNetworkError;
26
+ function withRequest({ method, url, headers, body }) {
27
+ const { error, debug } = (0, use_logger_1.useLogger)(withRequest.name);
28
+ return new rxjs_1.Observable(subscriber => {
29
+ const correlationId = (0, node_crypto_1.randomUUID)();
30
+ debug('requesting', { correlationId, method, url, headers, body });
31
+ (0, undici_1.request)(url, { method, headers, body })
32
+ .then(({ statusCode, body }) => __awaiter(this, void 0, void 0, function* () {
33
+ const json = yield body.json();
34
+ debug('received', {
35
+ correlationId,
36
+ method,
37
+ url,
38
+ headers,
39
+ body: json,
40
+ statusCode
41
+ });
42
+ if (statusCode !== 200) {
43
+ error(`errored`, {
44
+ method,
45
+ url,
46
+ headers,
47
+ body,
48
+ statusCode
49
+ });
50
+ subscriber.error(new RequestNetworkError(statusCode, () => json));
51
+ }
52
+ else {
53
+ subscriber.next({ timestamp: (0, use_timestamp_1.useTimestamp)(), payload: json });
54
+ }
55
+ }))
56
+ .catch((e) => {
57
+ error(`errored`, {
58
+ method,
59
+ url,
60
+ headers,
61
+ body,
62
+ error: e
63
+ });
64
+ subscriber.error(error);
65
+ })
66
+ .finally(() => subscriber.complete());
67
+ });
68
+ }
69
+ exports.withRequest = withRequest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantform/core",
3
- "version": "0.7.0-beta.4",
3
+ "version": "0.7.0-beta.40",
4
4
  "license": "MIT",
5
5
  "author": "Mateusz Majchrzak",
6
6
  "description": "Node.js library for building systematic trading strategies in reactive way.",
@@ -3,6 +3,12 @@ import { d, decimal } from '@lib/shared';
3
3
 
4
4
  export const AssetSelectorSeparator = ':';
5
5
 
6
+ export class MissingAssetError extends Error {
7
+ constructor(asset: AssetSelector) {
8
+ super(`Missing asset: ${asset}`);
9
+ }
10
+ }
11
+
6
12
  /**
7
13
  * Supposed to query specific @see Asset based on string notation.
8
14
  */
package/src/cli/dev.ts CHANGED
@@ -1,31 +1,15 @@
1
- import { join } from 'path';
2
- import { lastValueFrom } from 'rxjs';
3
-
4
1
  import build from '@lib/cli/build';
5
- import { buildDirectory } from '@lib/cli/internal/workspace';
6
- import { core } from '@lib/core';
7
- import { Module } from '@lib/module';
8
- import { strat } from '@lib/strat';
9
2
  import { paperExecutionMode } from '@lib/use-execution-mode';
10
3
 
4
+ import { Script } from './internal/script';
5
+
11
6
  export default async function (name: string, options: any) {
12
7
  if (await build()) {
13
8
  return;
14
9
  }
15
10
 
16
- const script = (await import(join(buildDirectory(), name))).default as ReturnType<
17
- typeof strat
18
- >;
19
-
20
- const module = new Module([
21
- ...core(),
22
- ...script.dependencies,
23
- paperExecutionMode({ recording: false })
24
- ]);
25
-
26
- const { act } = await module.awake();
27
-
28
- const output = await act(() => lastValueFrom(script.fn()));
11
+ const script = new Script(name, [paperExecutionMode({ recording: false })]);
12
+ const output = await script.run();
29
13
 
30
14
  console.log(output);
31
15
  }
@@ -0,0 +1,49 @@
1
+ import { join } from 'path';
2
+ import {
3
+ finalize,
4
+ firstValueFrom,
5
+ fromEvent,
6
+ last,
7
+ merge,
8
+ of,
9
+ switchMap,
10
+ take
11
+ } from 'rxjs';
12
+
13
+ import { core } from '@lib/core';
14
+ import { Dependency, Module } from '@lib/module';
15
+
16
+ import { buildDirectory } from './workspace';
17
+
18
+ export class Script {
19
+ constructor(
20
+ private readonly name: string,
21
+ private readonly dependencies: Dependency[]
22
+ ) {}
23
+
24
+ async run() {
25
+ const script = await import(join(buildDirectory(), this.name));
26
+ const module = new Module([...core(), ...script.onInstall(), ...this.dependencies]);
27
+
28
+ const { act } = await module.awake();
29
+
30
+ return await act(() => {
31
+ process.stdin.resume();
32
+
33
+ return firstValueFrom(
34
+ merge(
35
+ script.onAwake().pipe(last()),
36
+ fromEvent(process, 'exit'),
37
+ fromEvent(process, 'SIGINT'),
38
+ fromEvent(process, 'SIGUSR1'),
39
+ fromEvent(process, 'SIGUSR2'),
40
+ fromEvent(process, 'uncaughtException')
41
+ ).pipe(
42
+ take(1),
43
+ switchMap(it => script.onExit() ?? of(it)),
44
+ finalize(() => process.exit(0))
45
+ )
46
+ );
47
+ });
48
+ }
49
+ }
package/src/cli/pull.ts CHANGED
@@ -1,75 +1,15 @@
1
- import { join } from 'path';
2
-
3
1
  import build from '@lib/cli/build';
4
- import { buildDirectory } from '@lib/cli/internal/workspace';
5
- import { core } from '@lib/core';
6
- import { Module } from '@lib/module';
7
- import { strat } from '@lib/strat';
8
- import { paperExecutionMode } from '@lib/use-execution-mode';
9
- import { token } from '@lib/use-memo';
2
+ import { idleExecutionMode } from '@lib/use-execution-mode';
3
+
4
+ import { Script } from './internal/script';
10
5
 
11
6
  export default async function (name: string, instrument: string, options: any) {
12
7
  if (await build()) {
13
8
  return;
14
9
  }
15
- await import(join(buildDirectory(), 'index'));
16
-
17
- const script = (await import(join(buildDirectory(), name))) as ReturnType<typeof strat>;
18
-
19
- const module = new Module([
20
- ...core(),
21
- ...script.dependencies,
22
- paperExecutionMode({ recording: false })
23
- ]);
24
-
25
- const { act } = await module.awake();
26
-
27
- const o = await act(() => script.fn());
28
- console.log(module.get<any>(token));
29
-
30
- /*const builder = new SessionBuilder().useSessionId(
31
- options.id ? Number(options.id) : now()
32
- );
33
-
34
- await spawn(name, builder);
35
-
36
- const session = builder.paper();
37
-
38
- console.time('Pulling completed in');
39
-
40
- await session.awake();
41
-
42
- const bar = new SingleBar(
43
- {
44
- format: `Pulling ${instrument} [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}`
45
- },
46
- Presets.rect
47
- );
48
-
49
- const feed = new Feed(builder.storage('feed'));
50
- const from = options.from ? Date.parse(options.from) : builder.period.from;
51
- const to = options.to ? Date.parse(options.to) : builder.period.to;
52
-
53
- bar.start(100, 0);
54
-
55
- await session.aggregate.feed(
56
- instrumentOf(instrument),
57
- from,
58
- to,
59
- async (timestamp, events) => {
60
- const duration = to - from;
61
- const completed = timestamp - from;
62
-
63
- await feed.save(events);
64
-
65
- bar.update(Math.floor((completed / duration) * 100));
66
- }
67
- );
68
-
69
- bar.update(100);
70
- bar.stop();
71
10
 
72
- await session.dispose();
11
+ const script = new Script(name, [idleExecutionMode()]);
12
+ //const output = await script.run();
73
13
 
74
- console.timeLog('Pulling completed in');*/
14
+ //console.log(output);
75
15
  }
package/src/cli/replay.ts CHANGED
@@ -1,31 +1,19 @@
1
- import { join } from 'path';
2
- import { lastValueFrom } from 'rxjs';
3
-
4
1
  import build from '@lib/cli/build';
5
- import { buildDirectory } from '@lib/cli/internal/workspace';
6
- import { core } from '@lib/core';
7
- import { Dependency, Module } from '@lib/module';
8
2
  import { replayOptions } from '@lib/replay';
9
3
  import { replayExecutionMode } from '@lib/use-execution-mode';
10
4
 
5
+ import { Script } from './internal/script';
6
+
11
7
  export default async function (name: string, options: any) {
12
8
  if (await build()) {
13
9
  return;
14
10
  }
15
11
 
16
- const script = await import(join(buildDirectory(), name));
17
- const dependencies = script.module2 as Dependency[];
18
-
19
- const module = new Module([
20
- ...core(),
21
- ...dependencies,
12
+ const script = new Script(name, [
22
13
  replayOptions({ from: 0, to: Number.MAX_VALUE }),
23
14
  replayExecutionMode()
24
15
  ]);
25
-
26
- const { act } = await module.awake();
27
-
28
- const output = await act(() => lastValueFrom(script.default(options)));
16
+ const output = await script.run();
29
17
 
30
18
  console.log(output);
31
19
  }
package/src/cli/run.ts CHANGED
@@ -1,29 +1,15 @@
1
- import { join } from 'path';
2
- import { lastValueFrom } from 'rxjs';
3
-
4
1
  import build from '@lib/cli/build';
5
- import { buildDirectory } from '@lib/cli/internal/workspace';
6
- import { core } from '@lib/core';
7
- import { Dependency, Module } from '@lib/module';
8
2
  import { liveExecutionMode } from '@lib/use-execution-mode';
9
3
 
4
+ import { Script } from './internal/script';
5
+
10
6
  export default async function (name: string, options: any) {
11
7
  if (await build()) {
12
8
  return;
13
9
  }
14
10
 
15
- const script = await import(join(buildDirectory(), name));
16
- const dependencies = script.module2 as Dependency[];
17
-
18
- const module = new Module([
19
- ...core(),
20
- ...dependencies,
21
- liveExecutionMode({ recording: true })
22
- ]);
23
-
24
- const { act } = await module.awake();
25
-
26
- const output = await act(() => lastValueFrom(script.default(options)));
11
+ const script = new Script(name, [liveExecutionMode({ recording: true })]);
12
+ const output = await script.run();
27
13
 
28
14
  console.log(output);
29
15
  }
package/src/index.ts CHANGED
@@ -7,21 +7,12 @@ export * from '@lib/use-timestamp';
7
7
  export * from '@lib/simulator';
8
8
  export * from '@lib/make-test-module';
9
9
  export * from '@lib/core';
10
- export * from '@lib/use-state';
11
- export * from '@lib/replay/use-replay-coordinator';
12
10
  export * from '@lib/use-execution-mode';
13
- export * from '@lib/storage/use-storage';
14
- export * from '@lib/storage/use-cache';
11
+ export * from '@lib/storage';
15
12
  export * from '@lib/use-logger';
16
13
  export * from '@lib/replay';
17
- export * from '@lib/replay/use-replay-coordinator';
18
- export * from '@lib/use-socket';
19
- export * from '@lib/use-request';
20
- export * from '@lib/defined';
21
- export * from '@lib/as-readonly';
22
- export * from '@lib/use';
14
+ export * from '@lib/when-socket';
15
+ export * from '@lib/with-request';
16
+ export * from '@lib/with-memo';
23
17
  export * from '@lib/session';
24
- export * from '@lib/exclude';
25
- export * from '@lib/use-lock';
26
- export * from '@lib/strat';
27
- export * from '@lib/not-found';
18
+ export * from '@lib/operators';
@@ -5,6 +5,12 @@ import { Commission } from './commission/commission';
5
5
 
6
6
  export const InstrumentSelectorSeparator = '-';
7
7
 
8
+ export class MissingInstrumentError extends Error {
9
+ constructor(instrument: InstrumentSelector) {
10
+ super(`Missing instrument: ${instrument}`);
11
+ }
12
+ }
13
+
8
14
  export class InstrumentSelector {
9
15
  readonly id: string;
10
16
  readonly base: AssetSelector;
@@ -1,4 +1,4 @@
1
- import { Observable, tap } from 'rxjs';
1
+ import { Observable, Subject } from 'rxjs';
2
2
 
3
3
  import { core } from '@lib/core';
4
4
  import { Dependency, Module } from '@lib/module';
@@ -20,9 +20,9 @@ export const mockedFunc = <Func extends MockableFunction>(mockedFunc: Func) =>
20
20
  mockedFunc as jest.MockedFunction<typeof mockedFunc>;
21
21
 
22
22
  export function toArray<T>(observable: Observable<T>) {
23
- const array = Array.of<T>();
23
+ const array = Array.of<T | Error>();
24
24
 
25
- const clone = (it: T): T => {
25
+ const clone = (it: T | Error): T | Error => {
26
26
  if (typeof it === 'symbol') {
27
27
  return it;
28
28
  }
@@ -38,9 +38,29 @@ export function toArray<T>(observable: Observable<T>) {
38
38
  return it;
39
39
  };
40
40
 
41
- observable.pipe(tap(it => array.push(clone(it)))).subscribe();
41
+ observable.subscribe({
42
+ next: it => array.push(clone(it)),
43
+ error: it => array.push(it)
44
+ });
42
45
 
43
46
  return array;
44
47
  }
45
48
 
46
49
  export type InferObservableType<T> = T extends Observable<infer U> ? U : never;
50
+
51
+ export function mockSubject<
52
+ T extends jest.FunctionProperties<Required<T>>,
53
+ M extends keyof jest.FunctionProperties<Required<T>>
54
+ >(object: T, method: M) {
55
+ const subject = new Subject<
56
+ InferObservableType<ReturnType<jest.FunctionProperties<Required<T>>[M]>>
57
+ >();
58
+
59
+ jest
60
+ .spyOn<T, M>(object, method)
61
+ .mockReturnValue(
62
+ subject.asObservable() as ReturnType<jest.FunctionProperties<Required<T>>[M]>
63
+ );
64
+
65
+ return subject;
66
+ }
@@ -0,0 +1,18 @@
1
+ import { filter, map, Observable } from 'rxjs';
2
+
3
+ export function asReadonly<T>() {
4
+ return (input: Observable<T>) => input.pipe(map(it => it as Readonly<T>));
5
+ }
6
+
7
+ export function defined<T>() {
8
+ return (observable: Observable<T | undefined | null>) =>
9
+ observable.pipe(filter(it => it !== undefined && it !== null));
10
+ }
11
+
12
+ export function exclude<T, S extends symbol>(s: S) {
13
+ return (observable: Observable<T | S>) =>
14
+ observable.pipe(
15
+ filter(it => it !== s),
16
+ map(it => it as Exclude<T, typeof s>)
17
+ );
18
+ }
@@ -1,6 +1,8 @@
1
1
  export * from './use-replay';
2
- export * from './use-replay-coordinator';
2
+ export * from './use-replay-breakpoint';
3
3
  export * from './use-replay-options';
4
- export * from './use-replay-reader';
5
- export * from './use-replay-writer';
6
- export * from './with-replay';
4
+ export * from './use-replay-storage';
5
+ export * from './use-replay-storage-buffer';
6
+ export * from './use-replay-manager';
7
+ export * from './replay';
8
+ export * from './replay-guard';
@@ -0,0 +1,11 @@
1
+ import { Observable } from 'rxjs';
2
+
3
+ import { dependency } from '@lib/use-hash';
4
+
5
+ import { useReplayBreakpoint } from './use-replay-breakpoint';
6
+
7
+ export function replayGuard<T extends Array<dependency>, K>(
8
+ fn: (...args: T) => Observable<{ timestamp: number; payload: K }>
9
+ ): (...args: T) => Observable<{ timestamp: number; payload: K }> {
10
+ return (...args: T) => useReplayBreakpoint(fn(...args));
11
+ }
@@ -0,0 +1,13 @@
1
+ import { Observable } from 'rxjs';
2
+
3
+ import { dependency } from '@lib/use-hash';
4
+ import { withMemo } from '@lib/with-memo';
5
+
6
+ import { useReplay } from './use-replay';
7
+
8
+ export function replay<T extends Array<dependency>, K>(
9
+ fn: (...args: T) => Observable<{ timestamp: number; payload: K }>,
10
+ dependencies: dependency[]
11
+ ): (...args: T) => Observable<{ timestamp: number; payload: K }> {
12
+ return withMemo((...args: T) => useReplay(fn(...args), [...dependencies, ...args]));
13
+ }
@@ -0,0 +1,29 @@
1
+ import { finalize, Observable } from 'rxjs';
2
+
3
+ import { useExecutionMode } from '@lib/use-execution-mode';
4
+ import { useLogger } from '@lib/use-logger';
5
+
6
+ import { useReplayManager } from './use-replay-manager';
7
+
8
+ export function useReplayBreakpoint<T>(input: Observable<T>): Observable<T> {
9
+ const { isReplay } = useExecutionMode();
10
+
11
+ if (!isReplay) {
12
+ return input;
13
+ }
14
+
15
+ const { info } = useLogger('useReplayBreakpoint');
16
+ const { stop, tryContinue } = useReplayManager();
17
+
18
+ info('locking resource...');
19
+
20
+ stop();
21
+
22
+ return input.pipe(
23
+ finalize(() => {
24
+ info('unlocking resource');
25
+
26
+ tryContinue();
27
+ })
28
+ );
29
+ }
@@ -0,0 +1,106 @@
1
+ import { defer, filter, map, Observable, Subject } from 'rxjs';
2
+
3
+ import { dependency, useHash } from '@lib/use-hash';
4
+ import { useLogger } from '@lib/use-logger';
5
+ import { withMemo } from '@lib/with-memo';
6
+
7
+ import { useReplayOptions } from './use-replay-options';
8
+ import { useReplayStorageBuffer } from './use-replay-storage-buffer';
9
+
10
+ export const useReplayManager = withMemo(() => {
11
+ const { from, to } = useReplayOptions();
12
+ const { info } = useLogger('useReplayManager');
13
+
14
+ let timestamp = from;
15
+ let stopAcquire = 1;
16
+ const subscriptions = Array.of<ReturnType<typeof useReplayStorageBuffer<any>>>();
17
+
18
+ const stream$ = new Subject<
19
+ [ReturnType<typeof useReplayStorageBuffer<any>>, { timestamp: number; payload: any }]
20
+ >();
21
+
22
+ const getNextStorage = async () => {
23
+ let next: ReturnType<typeof useReplayStorageBuffer<any>> | undefined;
24
+
25
+ for (const cursor of subscriptions) {
26
+ if (cursor.size() == 0 && !cursor.completed()) {
27
+ await cursor.fetchNextPage(timestamp, to + 1);
28
+ }
29
+
30
+ if (cursor.peek()) {
31
+ if (!next || next.peek().timestamp > cursor.peek().timestamp) {
32
+ next = cursor;
33
+ }
34
+ }
35
+ }
36
+
37
+ return next;
38
+ };
39
+
40
+ const processNext = async () => {
41
+ const cursor = await getNextStorage();
42
+
43
+ if (!cursor || !cursor.peek()) {
44
+ stream$.complete();
45
+
46
+ return false;
47
+ }
48
+
49
+ const sample = cursor.dequeue();
50
+
51
+ timestamp = sample.timestamp;
52
+
53
+ stream$.next([cursor, sample]);
54
+
55
+ return true;
56
+ };
57
+
58
+ const next = async () => {
59
+ if (await processNext()) {
60
+ if (stopAcquire === 0) {
61
+ setImmediate(next);
62
+ }
63
+ }
64
+ };
65
+
66
+ const tryContinue = () => {
67
+ if (stopAcquire == 0) {
68
+ return;
69
+ }
70
+
71
+ stopAcquire = Math.max(0, stopAcquire - 1);
72
+
73
+ if (stopAcquire != 0) {
74
+ return;
75
+ }
76
+
77
+ next();
78
+ };
79
+
80
+ return {
81
+ timestamp() {
82
+ return timestamp;
83
+ },
84
+ stop() {
85
+ stopAcquire++;
86
+ },
87
+ tryContinue,
88
+ when<T>(dependencies: dependency[]): Observable<{ timestamp: number; payload: T }> {
89
+ const storage = useReplayStorageBuffer<T>(dependencies);
90
+
91
+ if (!subscriptions.includes(storage)) {
92
+ info('subscribing to replay', useHash(dependencies));
93
+ subscriptions.push(storage);
94
+ }
95
+
96
+ return defer(() => {
97
+ tryContinue();
98
+
99
+ return stream$.pipe(
100
+ filter(([cur]) => cur === storage),
101
+ map(([, it]) => ({ timestamp: it.timestamp, payload: it.payload as T }))
102
+ );
103
+ });
104
+ }
105
+ };
106
+ });
@@ -0,0 +1,43 @@
1
+ import { between } from '@lib/storage';
2
+ import { dependency } from '@lib/use-hash';
3
+ import { withMemo } from '@lib/with-memo';
4
+
5
+ import { useReplayStorage } from './use-replay-storage';
6
+
7
+ export const useReplayStorageBuffer = withMemo(<T>(dependencies: dependency[]) => {
8
+ const { query } = useReplayStorage<T>(dependencies);
9
+
10
+ let page = new Array<{ timestamp: number; payload: T }>();
11
+ let index = 0;
12
+ let completed = false;
13
+
14
+ return {
15
+ size() {
16
+ return page.length - index;
17
+ },
18
+ peek() {
19
+ return page[index];
20
+ },
21
+ dequeue() {
22
+ return page[index++];
23
+ },
24
+ completed() {
25
+ return completed;
26
+ },
27
+ async fetchNextPage(from: number, to: number) {
28
+ if (completed) {
29
+ return;
30
+ }
31
+
32
+ index = 0;
33
+
34
+ page = await query({
35
+ where: {
36
+ timestamp: between(from, to)
37
+ },
38
+ limit: 10000
39
+ });
40
+ completed = page.length == 0;
41
+ }
42
+ };
43
+ });