socket-function 0.9.0 → 0.9.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 (45) hide show
  1. package/.eslintrc.js +50 -50
  2. package/SocketFunction.ts +280 -280
  3. package/SocketFunctionTypes.ts +90 -90
  4. package/hot/HotReloadController.ts +105 -105
  5. package/mobx/UrlParam.ts +39 -39
  6. package/mobx/observer.tsx +49 -49
  7. package/mobx/promiseToObservable.tsx +41 -41
  8. package/package.json +30 -28
  9. package/require/CSSShim.ts +19 -19
  10. package/require/RequireController.ts +252 -252
  11. package/require/buffer.js +2368 -2368
  12. package/require/compileFlags.ts +44 -44
  13. package/require/require.html +13 -13
  14. package/require/require.js +462 -456
  15. package/spec.txt +115 -115
  16. package/src/CallFactory.ts +389 -389
  17. package/src/JSONLACKS/JSONLACKS.generated.js +17 -17
  18. package/src/JSONLACKS/JSONLACKS.pegjs +247 -247
  19. package/src/JSONLACKS/JSONLACKS.ts +429 -375
  20. package/src/args.ts +21 -21
  21. package/src/batching.ts +170 -129
  22. package/src/caching.ts +318 -314
  23. package/src/callHTTPHandler.ts +203 -203
  24. package/src/callManager.ts +134 -134
  25. package/src/certStore.ts +29 -29
  26. package/src/fixLargeNetworkCalls.ts +8 -8
  27. package/src/formatting/colors.ts +78 -78
  28. package/src/formatting/format.ts +160 -156
  29. package/src/formatting/logColors.ts +17 -17
  30. package/src/misc.ts +302 -171
  31. package/src/nodeCache.ts +92 -92
  32. package/src/nodeProxy.ts +54 -54
  33. package/src/profiling/getOwnTime.ts +142 -142
  34. package/src/profiling/measure.ts +273 -244
  35. package/src/profiling/stats.ts +212 -212
  36. package/src/profiling/tcpLagProxy.ts +63 -63
  37. package/src/storagePath.ts +10 -10
  38. package/src/tlsParsing.ts +96 -96
  39. package/src/types.ts +8 -8
  40. package/src/webSocketServer.ts +250 -250
  41. package/test/client.css +2 -2
  42. package/test/client.ts +46 -46
  43. package/test/server.ts +43 -43
  44. package/test/shared.ts +52 -52
  45. package/tsconfig.json +26 -26
@@ -1,91 +1,91 @@
1
- /// <reference path="./require/RequireController.ts" />
2
-
3
- module.allowclient = true;
4
-
5
- import { SocketFunction } from "./SocketFunction";
6
- import { getCallObj } from "./src/nodeProxy";
7
- import { Args, MaybePromise } from "./src/types";
8
-
9
- export const socket = Symbol("socket");
10
-
11
- export type SocketExposedInterface = {
12
- [functionName: string]: (...args: any[]) => Promise<unknown>;
13
- };
14
- export type SocketInternalInterface = {
15
- [functionName: string]: {
16
- [getCallObj]: (...args: any[]) => FullCallType;
17
- (...args: any[]): Promise<unknown>;
18
- }
19
- }
20
- export type SocketExposedInterfaceClass = {
21
- //new(): SocketExposedInterface;
22
- new(): unknown;
23
- prototype: unknown;
24
- };
25
- export interface SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
26
- [functionName: string]: {
27
- /** Indicates with the same input, we give the same output, forever,
28
- * independent of code changes. This only works for data storage.
29
- */
30
- dataImmutable?: boolean;
31
- hooks?: SocketFunctionHook<ExposedType>[];
32
- clientHooks?: SocketFunctionClientHook<ExposedType>[];
33
- };
34
- }
35
-
36
- export interface CallType {
37
- classGuid: string;
38
- functionName: string;
39
- args: unknown[];
40
- }
41
- export interface FullCallType extends CallType {
42
- nodeId: string;
43
- }
44
-
45
- export interface SocketFunctionHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
46
- (config: HookContext<ExposedType>): MaybePromise<void>;
47
- /** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
48
- clientHook?: SocketFunctionClientHook<ExposedType>;
49
- }
50
- export type HookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
51
- call: FullCallType;
52
- // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
53
- overrideResult?: unknown;
54
- };
55
-
56
- export type ClientHookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
57
- call: FullCallType;
58
- // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
59
- overrideResult?: unknown;
60
- connectionId: { nodeId: string };
61
- };
62
- export interface SocketFunctionClientHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
63
- (config: ClientHookContext<ExposedType>): MaybePromise<void>;
64
- }
65
-
66
- export interface SocketRegistered<ExposedType = any> {
67
- nodes: {
68
- // NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
69
- // then turn into a nodeId, which they can then check permissions on themself).
70
- [nodeId: string]: {
71
- [functionName in keyof ExposedType]: ExposedType[functionName] & {
72
- [getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
73
- }
74
- };
75
- };
76
- _classGuid: string;
77
- }
78
- export type CallerContext = Readonly<CallerContextBase>;
79
- export type CallerContextBase = {
80
- // IMPORTANT! Do not pass nodeId to other nodes with the intention of having
81
- // them call functions directly using nodeId. Instead pass location, and have them use connect.
82
- // - nodeId will be unique per thread, so is only useful for temporary communcation. If you want
83
- // a more permanent identity, you must derive it from certInfo yourself.
84
- nodeId: string;
85
-
86
- // The nodeId they contacted. This is useful to determine their intention (otherwise
87
- // requests can be redirected to us and would accept them, even though they are being
88
- // blatantly MITMed).
89
- // IF they are the server, calling us back, then this will just be ""
90
- localNodeId: string;
1
+ /// <reference path="./require/RequireController.ts" />
2
+
3
+ module.allowclient = true;
4
+
5
+ import { SocketFunction } from "./SocketFunction";
6
+ import { getCallObj } from "./src/nodeProxy";
7
+ import { Args, MaybePromise } from "./src/types";
8
+
9
+ export const socket = Symbol("socket");
10
+
11
+ export type SocketExposedInterface = {
12
+ [functionName: string]: (...args: any[]) => Promise<unknown>;
13
+ };
14
+ export type SocketInternalInterface = {
15
+ [functionName: string]: {
16
+ [getCallObj]: (...args: any[]) => FullCallType;
17
+ (...args: any[]): Promise<unknown>;
18
+ }
19
+ }
20
+ export type SocketExposedInterfaceClass = {
21
+ //new(): SocketExposedInterface;
22
+ new(): unknown;
23
+ prototype: unknown;
24
+ };
25
+ export interface SocketExposedShape<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
26
+ [functionName: string]: {
27
+ /** Indicates with the same input, we give the same output, forever,
28
+ * independent of code changes. This only works for data storage.
29
+ */
30
+ dataImmutable?: boolean;
31
+ hooks?: SocketFunctionHook<ExposedType>[];
32
+ clientHooks?: SocketFunctionClientHook<ExposedType>[];
33
+ };
34
+ }
35
+
36
+ export interface CallType {
37
+ classGuid: string;
38
+ functionName: string;
39
+ args: unknown[];
40
+ }
41
+ export interface FullCallType extends CallType {
42
+ nodeId: string;
43
+ }
44
+
45
+ export interface SocketFunctionHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
46
+ (config: HookContext<ExposedType>): MaybePromise<void>;
47
+ /** NOTE: This is useful when we need a clientside hook to set up state specifically for our serverside hook. */
48
+ clientHook?: SocketFunctionClientHook<ExposedType>;
49
+ }
50
+ export type HookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
51
+ call: FullCallType;
52
+ // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
53
+ overrideResult?: unknown;
54
+ };
55
+
56
+ export type ClientHookContext<ExposedType extends SocketExposedInterface = SocketExposedInterface> = {
57
+ call: FullCallType;
58
+ // If the result is overriden, we continue evaluating hooks BUT DO NOT perform the final call
59
+ overrideResult?: unknown;
60
+ connectionId: { nodeId: string };
61
+ };
62
+ export interface SocketFunctionClientHook<ExposedType extends SocketExposedInterface = SocketExposedInterface> {
63
+ (config: ClientHookContext<ExposedType>): MaybePromise<void>;
64
+ }
65
+
66
+ export interface SocketRegistered<ExposedType = any> {
67
+ nodes: {
68
+ // NOTE: Don't pass around nodeId to other nodes, instead pass around NetworkLocation (which they
69
+ // then turn into a nodeId, which they can then check permissions on themself).
70
+ [nodeId: string]: {
71
+ [functionName in keyof ExposedType]: ExposedType[functionName] & {
72
+ [getCallObj]: (...args: Args<ExposedType[functionName]>) => FullCallType;
73
+ }
74
+ };
75
+ };
76
+ _classGuid: string;
77
+ }
78
+ export type CallerContext = Readonly<CallerContextBase>;
79
+ export type CallerContextBase = {
80
+ // IMPORTANT! Do not pass nodeId to other nodes with the intention of having
81
+ // them call functions directly using nodeId. Instead pass location, and have them use connect.
82
+ // - nodeId will be unique per thread, so is only useful for temporary communcation. If you want
83
+ // a more permanent identity, you must derive it from certInfo yourself.
84
+ nodeId: string;
85
+
86
+ // The nodeId they contacted. This is useful to determine their intention (otherwise
87
+ // requests can be redirected to us and would accept them, even though they are being
88
+ // blatantly MITMed).
89
+ // IF they are the server, calling us back, then this will just be ""
90
+ localNodeId: string;
91
91
  };
@@ -1,106 +1,106 @@
1
- /// <reference path="../../typenode/index.d.ts" />
2
- /// <reference path="../require/RequireController.ts" />
3
- module.allowclient = true;
4
-
5
- import { SocketFunction } from "../SocketFunction";
6
- import { cache, lazy } from "../src/caching";
7
- import * as fs from "fs";
8
- import debugbreak from "debugbreak";
9
- import { isNode } from "../src/misc";
10
- import { red } from "../src/formatting/logColors";
11
-
12
- /** Enables some hot reload functionality.
13
- * - Triggers a refresh clientside
14
- * - Triggers a reload server, for modules marked with `module.hotreload`
15
- */
16
- export function watchFilesAndTriggerHotReloading(noAutomaticBrowserWatch = false) {
17
-
18
- SocketFunction.expose(HotReloadController);
19
- if (!isNode()) {
20
- if (!noAutomaticBrowserWatch) {
21
- HotReloadController.nodes[SocketFunction.locationNode()]
22
- .watchFiles()
23
- .catch(e => console.error("watchFiles error", e))
24
- ;
25
- }
26
- }
27
- setInterval(() => {
28
- for (let module of Object.values(require.cache)) {
29
- if (!module) continue;
30
- hotReloadModule(module);
31
- }
32
- }, 5000);
33
- }
34
-
35
- declare global {
36
- namespace NodeJS {
37
- interface Module {
38
- hotreload?: boolean;
39
- noserverhotreload?: boolean;
40
- }
41
- }
42
- }
43
-
44
- const hotReloadModule = cache((module: NodeJS.Module) => {
45
- if (!module.updateContents) return;
46
- fs.watchFile(module.filename, { persistent: false, interval: 1000 }, (curr, prev) => {
47
- if (curr.mtime.getTime() === prev.mtime.getTime()) return;
48
- console.log(`Hot reloading due to change: ${module.filename}`);
49
- module.updateContents?.();
50
- if (isNode()) {
51
- if (
52
- module.hotreload
53
- // A fairly big hack (as this could just be in a string, or something similar), but... it also VERY useful
54
- || module.moduleContents?.includes("\nmodule.hotreload = true;" + "\n")
55
- || module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
56
- ) {
57
- console.log(`Reloading ${module.id}`);
58
- try {
59
- module.loaded = false;
60
- module.load(module.id);
61
- } catch (e) {
62
- console.error(red(`Error hot reloading ${module.id}`));
63
- console.error(e);
64
- }
65
- }
66
- }
67
- triggerClientSideReload();
68
- });
69
- });
70
- let reloadTriggering = false;
71
- let clientWatcherNodes = new Set<string>();
72
- function triggerClientSideReload() {
73
- if (reloadTriggering) return;
74
- reloadTriggering = true;
75
- setTimeout(async () => {
76
- reloadTriggering = false;
77
- for (let clientNodeId of clientWatcherNodes) {
78
- console.log(`Notifying client of hot reload: ${clientNodeId}`);
79
- HotReloadController.nodes[clientNodeId].fileUpdated().catch(() => {
80
- console.log(`Removing erroring client: ${clientNodeId}`);
81
- clientWatcherNodes.delete(clientNodeId);
82
- });
83
- }
84
- }, 300);
85
- }
86
-
87
- class HotReloadControllerBase {
88
- // TODO: Also hot reload when we reconnect to the server, as it is likely setup will need to
89
- // be rerun in that case as well (for example, we need to call watchFiles again!)
90
- async watchFiles() {
91
- let callerId = SocketFunction.getCaller().nodeId;
92
- clientWatcherNodes.add(callerId);
93
- }
94
- async fileUpdated() {
95
- document.location.reload();
96
- }
97
- }
98
-
99
- export const HotReloadController = SocketFunction.register(
100
- "HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5",
101
- new HotReloadControllerBase(),
102
- () => ({
103
- watchFiles: {},
104
- fileUpdated: {}
105
- })
1
+ /// <reference path="../../typenode/index.d.ts" />
2
+ /// <reference path="../require/RequireController.ts" />
3
+ module.allowclient = true;
4
+
5
+ import { SocketFunction } from "../SocketFunction";
6
+ import { cache, lazy } from "../src/caching";
7
+ import * as fs from "fs";
8
+ import debugbreak from "debugbreak";
9
+ import { isNode } from "../src/misc";
10
+ import { red } from "../src/formatting/logColors";
11
+
12
+ /** Enables some hot reload functionality.
13
+ * - Triggers a refresh clientside
14
+ * - Triggers a reload server, for modules marked with `module.hotreload`
15
+ */
16
+ export function watchFilesAndTriggerHotReloading(noAutomaticBrowserWatch = false) {
17
+
18
+ SocketFunction.expose(HotReloadController);
19
+ if (!isNode()) {
20
+ if (!noAutomaticBrowserWatch) {
21
+ HotReloadController.nodes[SocketFunction.locationNode()]
22
+ .watchFiles()
23
+ .catch(e => console.error("watchFiles error", e))
24
+ ;
25
+ }
26
+ }
27
+ setInterval(() => {
28
+ for (let module of Object.values(require.cache)) {
29
+ if (!module) continue;
30
+ hotReloadModule(module);
31
+ }
32
+ }, 5000);
33
+ }
34
+
35
+ declare global {
36
+ namespace NodeJS {
37
+ interface Module {
38
+ hotreload?: boolean;
39
+ noserverhotreload?: boolean;
40
+ }
41
+ }
42
+ }
43
+
44
+ const hotReloadModule = cache((module: NodeJS.Module) => {
45
+ if (!module.updateContents) return;
46
+ fs.watchFile(module.filename, { persistent: false, interval: 1000 }, (curr, prev) => {
47
+ if (curr.mtime.getTime() === prev.mtime.getTime()) return;
48
+ console.log(`Hot reloading due to change: ${module.filename}`);
49
+ module.updateContents?.();
50
+ if (isNode()) {
51
+ if (
52
+ module.hotreload
53
+ // A fairly big hack (as this could just be in a string, or something similar), but... it also VERY useful
54
+ || module.moduleContents?.includes("\nmodule.hotreload = true;" + "\n")
55
+ || module.moduleContents?.includes("\r\nmodule.hotreload = true;" + "\r\n")
56
+ ) {
57
+ console.log(`Reloading ${module.id}`);
58
+ try {
59
+ module.loaded = false;
60
+ module.load(module.id);
61
+ } catch (e) {
62
+ console.error(red(`Error hot reloading ${module.id}`));
63
+ console.error(e);
64
+ }
65
+ }
66
+ }
67
+ triggerClientSideReload();
68
+ });
69
+ });
70
+ let reloadTriggering = false;
71
+ let clientWatcherNodes = new Set<string>();
72
+ function triggerClientSideReload() {
73
+ if (reloadTriggering) return;
74
+ reloadTriggering = true;
75
+ setTimeout(async () => {
76
+ reloadTriggering = false;
77
+ for (let clientNodeId of clientWatcherNodes) {
78
+ console.log(`Notifying client of hot reload: ${clientNodeId}`);
79
+ HotReloadController.nodes[clientNodeId].fileUpdated().catch(() => {
80
+ console.log(`Removing erroring client: ${clientNodeId}`);
81
+ clientWatcherNodes.delete(clientNodeId);
82
+ });
83
+ }
84
+ }, 300);
85
+ }
86
+
87
+ class HotReloadControllerBase {
88
+ // TODO: Also hot reload when we reconnect to the server, as it is likely setup will need to
89
+ // be rerun in that case as well (for example, we need to call watchFiles again!)
90
+ async watchFiles() {
91
+ let callerId = SocketFunction.getCaller().nodeId;
92
+ clientWatcherNodes.add(callerId);
93
+ }
94
+ async fileUpdated() {
95
+ document.location.reload();
96
+ }
97
+ }
98
+
99
+ export const HotReloadController = SocketFunction.register(
100
+ "HotReloadController-032b2250-3aac-4187-8c95-75412742b8f5",
101
+ new HotReloadControllerBase(),
102
+ () => ({
103
+ watchFiles: {},
104
+ fileUpdated: {}
105
+ })
106
106
  );
package/mobx/UrlParam.ts CHANGED
@@ -1,40 +1,40 @@
1
- import { observable } from "mobx";
2
- import { isNode } from "../src/misc";
3
-
4
- // TODO: Batch url state changes
5
- // TODO: Create actual links, that a tag can use
6
- // - Then after that, support not reloading the page, instead just setting the observables,
7
- // for faster navigation.
8
-
9
- export class UrlParam<T> {
10
- constructor(private key: string, private defaultValue: T) { }
11
- valueSeqNum = observable({ value: 1 });
12
- public get(): T {
13
- urlBackSeqNum.value;
14
- this.valueSeqNum.value;
15
- let value = new URL(location.href).searchParams.get(this.key);
16
- if (value === null) {
17
- return this.defaultValue;
18
- }
19
- return JSON.parse(value) as T;
20
- }
21
- public set(value: T) {
22
- let url = new URL(location.href);
23
- url.searchParams.set(this.key, JSON.stringify(value));
24
- history.pushState({}, "", url.toString());
25
- this.valueSeqNum.value++;
26
- }
27
- public reset() {
28
- let url = new URL(location.href);
29
- url.searchParams.delete(this.key);
30
- history.pushState({}, "", url.toString());
31
- this.valueSeqNum.value++;
32
- }
33
- }
34
-
35
- let urlBackSeqNum = observable({ value: 1 });
36
- if (!isNode()) {
37
- window.addEventListener("popstate", () => {
38
- urlBackSeqNum.value++;
39
- });
1
+ import { observable } from "mobx";
2
+ import { isNode } from "../src/misc";
3
+
4
+ // TODO: Batch url state changes
5
+ // TODO: Create actual links, that a tag can use
6
+ // - Then after that, support not reloading the page, instead just setting the observables,
7
+ // for faster navigation.
8
+
9
+ export class UrlParam<T> {
10
+ constructor(private key: string, private defaultValue: T) { }
11
+ valueSeqNum = observable({ value: 1 });
12
+ public get(): T {
13
+ urlBackSeqNum.value;
14
+ this.valueSeqNum.value;
15
+ let value = new URL(location.href).searchParams.get(this.key);
16
+ if (value === null) {
17
+ return this.defaultValue;
18
+ }
19
+ return JSON.parse(value) as T;
20
+ }
21
+ public set(value: T) {
22
+ let url = new URL(location.href);
23
+ url.searchParams.set(this.key, JSON.stringify(value));
24
+ history.pushState({}, "", url.toString());
25
+ this.valueSeqNum.value++;
26
+ }
27
+ public reset() {
28
+ let url = new URL(location.href);
29
+ url.searchParams.delete(this.key);
30
+ history.pushState({}, "", url.toString());
31
+ this.valueSeqNum.value++;
32
+ }
33
+ }
34
+
35
+ let urlBackSeqNum = observable({ value: 1 });
36
+ if (!isNode()) {
37
+ window.addEventListener("popstate", () => {
38
+ urlBackSeqNum.value++;
39
+ });
40
40
  }
package/mobx/observer.tsx CHANGED
@@ -1,50 +1,50 @@
1
- import type preact from "preact";
2
- import { setFlag } from "../require/compileFlags";
3
-
4
- import { observable, Reaction } from "mobx";
5
-
6
- setFlag(require, "mobx", "allowclient", true);
7
- setFlag(require, "preact", "allowclient", true);
8
-
9
- let globalConstructOrder = 0;
10
-
11
- export function observer<
12
- T extends {
13
- new(...args: any[]): {
14
- render(): preact.ComponentChild;
15
- forceUpdate(callback?: () => void): void;
16
- componentWillUnmount?(): void;
17
- }
18
- }
19
- >(
20
- Constructor: T
21
- ) {
22
- let name = Constructor.name;
23
- return class extends Constructor {
24
- // NOTE: This is completely valid javascript. For some reason (https://github.com/microsoft/TypeScript/pull/12065#issuecomment-270205513)
25
- // the typescript team decided, whatever, just make it an error, even though it isn't in es6 ("we should simplify ES6' semantics").
26
- // So, instead of simplifying ES6 semantics, lets give ourself better info for debugging...
27
- // @ts-ignore
28
- static get name() { return Constructor.name; }
29
-
30
- // It is always true, that a parent has a constructOrder < a child's constructOrder
31
- constructOrder = globalConstructOrder++;
32
-
33
- reaction = new Reaction(`render.${name}.${this.constructOrder}`, () => {
34
- super.forceUpdate();
35
- });
36
-
37
- componentWillUnmount() {
38
- this.reaction.dispose();
39
- super.componentWillUnmount?.();
40
- }
41
-
42
- render() {
43
- let output: preact.ComponentChild;
44
- this.reaction.track(() => {
45
- output = super.render();
46
- });
47
- return output;
48
- }
49
- };
1
+ import type preact from "preact";
2
+ import { setFlag } from "../require/compileFlags";
3
+
4
+ import { observable, Reaction } from "mobx";
5
+
6
+ setFlag(require, "mobx", "allowclient", true);
7
+ setFlag(require, "preact", "allowclient", true);
8
+
9
+ let globalConstructOrder = 0;
10
+
11
+ export function observer<
12
+ T extends {
13
+ new(...args: any[]): {
14
+ render(): preact.ComponentChild;
15
+ forceUpdate(callback?: () => void): void;
16
+ componentWillUnmount?(): void;
17
+ }
18
+ }
19
+ >(
20
+ Constructor: T
21
+ ) {
22
+ let name = Constructor.name;
23
+ return class extends Constructor {
24
+ // NOTE: This is completely valid javascript. For some reason (https://github.com/microsoft/TypeScript/pull/12065#issuecomment-270205513)
25
+ // the typescript team decided, whatever, just make it an error, even though it isn't in es6 ("we should simplify ES6' semantics").
26
+ // So, instead of simplifying ES6 semantics, lets give ourself better info for debugging...
27
+ // @ts-ignore
28
+ static get name() { return Constructor.name; }
29
+
30
+ // It is always true, that a parent has a constructOrder < a child's constructOrder
31
+ constructOrder = globalConstructOrder++;
32
+
33
+ reaction = new Reaction(`render.${name}.${this.constructOrder}`, () => {
34
+ super.forceUpdate();
35
+ });
36
+
37
+ componentWillUnmount() {
38
+ this.reaction.dispose();
39
+ super.componentWillUnmount?.();
40
+ }
41
+
42
+ render() {
43
+ let output: preact.ComponentChild;
44
+ this.reaction.track(() => {
45
+ output = super.render();
46
+ });
47
+ return output;
48
+ }
49
+ };
50
50
  }
@@ -1,41 +1,41 @@
1
- import { observable, Reaction, IObservable } from "mobx";
2
- import { cacheLimited } from "../src/caching";
3
-
4
- export interface InternalResult {
5
- result: { value: unknown } | undefined;
6
- }
7
- export function promiseToObservable<T>(promise: Promise<T>, staleValue?: T): { value: T | undefined } {
8
- let isDone = false;
9
- let error: unknown;
10
- let result: T | undefined;
11
-
12
- let internalResult: InternalResult = { result: undefined };
13
-
14
- let isDoneTrigger = observable({
15
- value: false
16
- });
17
- promise.then(
18
- r => {
19
- internalResult.result = { value: r };
20
- result = r;
21
- isDoneTrigger.value = true;
22
- isDone = true;
23
- },
24
- e => {
25
- error = e;
26
- isDoneTrigger.value = true;
27
- isDone = true;
28
- }
29
- );
30
-
31
- return Object.assign({
32
- get value() {
33
- if (isDone) {
34
- if (error) throw error;
35
- return result;
36
- }
37
- isDoneTrigger.value;
38
- return staleValue;
39
- }
40
- }, { internalResult });
41
- }
1
+ import { observable, Reaction, IObservable } from "mobx";
2
+ import { cacheLimited } from "../src/caching";
3
+
4
+ export interface InternalResult {
5
+ result: { value: unknown } | undefined;
6
+ }
7
+ export function promiseToObservable<T>(promise: Promise<T>, staleValue?: T): { value: T | undefined } {
8
+ let isDone = false;
9
+ let error: unknown;
10
+ let result: T | undefined;
11
+
12
+ let internalResult: InternalResult = { result: undefined };
13
+
14
+ let isDoneTrigger = observable({
15
+ value: false
16
+ });
17
+ promise.then(
18
+ r => {
19
+ internalResult.result = { value: r };
20
+ result = r;
21
+ isDoneTrigger.value = true;
22
+ isDone = true;
23
+ },
24
+ e => {
25
+ error = e;
26
+ isDoneTrigger.value = true;
27
+ isDone = true;
28
+ }
29
+ );
30
+
31
+ return Object.assign({
32
+ get value() {
33
+ if (isDone) {
34
+ if (error) throw error;
35
+ return result;
36
+ }
37
+ isDoneTrigger.value;
38
+ return staleValue;
39
+ }
40
+ }, { internalResult });
41
+ }