@spearwolf/shadow-objects 0.32.0 → 0.34.0
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.
- package/CHANGELOG.md +333 -10
- package/README.md +100 -20
- package/bundle.js +5 -54
- package/package.json +7 -3
- package/src/ChangeTrailRefusedError.d.ts +27 -0
- package/src/ChangeTrailRefusedError.d.ts.map +1 -0
- package/src/ChangeTrailRefusedError.js +12 -0
- package/src/ChangeTrailRefusedError.js.map +7 -0
- package/src/EntityUuidInUseError.d.ts +19 -0
- package/src/EntityUuidInUseError.d.ts.map +1 -0
- package/src/EntityUuidInUseError.js +11 -0
- package/src/EntityUuidInUseError.js.map +7 -0
- package/src/WorkerTimeoutError.d.ts +25 -0
- package/src/WorkerTimeoutError.d.ts.map +1 -0
- package/src/WorkerTimeoutError.js +12 -0
- package/src/WorkerTimeoutError.js.map +7 -0
- package/src/bundle.d.ts +1 -1
- package/src/bundle.d.ts.map +1 -1
- package/src/bundle.js +1 -1
- package/src/bundle.js.map +2 -2
- package/src/constants.d.ts +9 -2
- package/src/constants.d.ts.map +1 -1
- package/src/constants.js +1 -1
- package/src/constants.js.map +2 -2
- package/src/create-worker.bundle.d.ts +1 -1
- package/src/create-worker.bundle.d.ts.map +1 -1
- package/src/create-worker.bundle.js.map +1 -1
- package/src/create-worker.d.ts +1 -1
- package/src/create-worker.d.ts.map +1 -1
- package/src/create-worker.js.map +1 -1
- package/src/elements/ShaeElement.d.ts +70 -2
- package/src/elements/ShaeElement.d.ts.map +1 -1
- package/src/elements/ShaeElement.js +129 -25
- package/src/elements/ShaeElement.js.map +3 -3
- package/src/elements/ShaeEntElement.d.ts +43 -5
- package/src/elements/ShaeEntElement.d.ts.map +1 -1
- package/src/elements/ShaeEntElement.js +399 -170
- package/src/elements/ShaeEntElement.js.map +2 -2
- package/src/elements/ShaeLifecycleElement.d.ts +71 -0
- package/src/elements/ShaeLifecycleElement.d.ts.map +1 -0
- package/src/elements/ShaeLifecycleElement.js +83 -0
- package/src/elements/ShaeLifecycleElement.js.map +7 -0
- package/src/elements/ShaePropElement.d.ts +57 -7
- package/src/elements/ShaePropElement.d.ts.map +1 -1
- package/src/elements/ShaePropElement.js +241 -220
- package/src/elements/ShaePropElement.js.map +2 -2
- package/src/elements/ShaeWorkerElement.d.ts +49 -5
- package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
- package/src/elements/ShaeWorkerElement.js +230 -74
- package/src/elements/ShaeWorkerElement.js.map +2 -2
- package/src/elements/constants.d.ts +6 -0
- package/src/elements/constants.d.ts.map +1 -1
- package/src/elements/constants.js +12 -0
- package/src/elements/constants.js.map +2 -2
- package/src/elements/deferredTeardown.d.ts +24 -0
- package/src/elements/deferredTeardown.d.ts.map +1 -0
- package/src/elements/deferredTeardown.js +30 -0
- package/src/elements/deferredTeardown.js.map +7 -0
- package/src/elements/displayContentsRule.d.ts +25 -0
- package/src/elements/displayContentsRule.d.ts.map +1 -0
- package/src/elements/displayContentsRule.js +37 -0
- package/src/elements/displayContentsRule.js.map +7 -0
- package/src/elements/events.d.ts +10 -7
- package/src/elements/events.d.ts.map +1 -1
- package/src/elements/forwardCustomEvents.d.ts +19 -0
- package/src/elements/forwardCustomEvents.d.ts.map +1 -0
- package/src/elements/forwardCustomEvents.js +47 -0
- package/src/elements/forwardCustomEvents.js.map +7 -0
- package/src/elements/hostedSlots.d.ts +36 -0
- package/src/elements/hostedSlots.d.ts.map +1 -0
- package/src/elements/hostedSlots.js +118 -0
- package/src/elements/hostedSlots.js.map +7 -0
- package/src/elements/parentRemoval.d.ts +18 -0
- package/src/elements/parentRemoval.d.ts.map +1 -0
- package/src/elements/parentRemoval.js +60 -0
- package/src/elements/parentRemoval.js.map +7 -0
- package/src/elements/propValueConverters.d.ts +3 -0
- package/src/elements/propValueConverters.d.ts.map +1 -0
- package/src/elements/propValueConverters.js +54 -0
- package/src/elements/propValueConverters.js.map +7 -0
- package/src/elements/requestEntAncestor.d.ts +32 -0
- package/src/elements/requestEntAncestor.d.ts.map +1 -0
- package/src/elements/requestEntAncestor.js +16 -0
- package/src/elements/requestEntAncestor.js.map +7 -0
- package/src/elements.js.map +1 -1
- package/src/in-the-dark/Entity.d.ts +113 -3
- package/src/in-the-dark/Entity.d.ts.map +1 -1
- package/src/in-the-dark/Entity.js +292 -63
- package/src/in-the-dark/Entity.js.map +3 -3
- package/src/in-the-dark/Kernel.d.ts +81 -17
- package/src/in-the-dark/Kernel.d.ts.map +1 -1
- package/src/in-the-dark/Kernel.js +449 -392
- package/src/in-the-dark/Kernel.js.map +3 -3
- package/src/in-the-dark/Registry.d.ts +1 -1
- package/src/in-the-dark/Registry.d.ts.map +1 -1
- package/src/in-the-dark/Registry.js +99 -26
- package/src/in-the-dark/Registry.js.map +2 -2
- package/src/in-the-dark/ShadowObject.d.ts +1 -1
- package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
- package/src/in-the-dark/ShadowObject.js +3 -2
- package/src/in-the-dark/ShadowObject.js.map +2 -2
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts +126 -0
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js +596 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
- package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
- package/src/in-the-dark/SignalsPath.js +2 -2
- package/src/in-the-dark/SignalsPath.js.map +2 -2
- package/src/in-the-dark/events.js.map +1 -1
- package/src/in-the-dark/importModule.d.ts +7 -0
- package/src/in-the-dark/importModule.d.ts.map +1 -1
- package/src/in-the-dark/importModule.js +8 -5
- package/src/in-the-dark/importModule.js.map +2 -2
- package/src/index.d.ts +7 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +5 -1
- package/src/index.js.map +2 -2
- package/src/shadow-objects.d.ts +2 -0
- package/src/shadow-objects.d.ts.map +1 -1
- package/src/shadow-objects.js +2 -0
- package/src/shadow-objects.js.map +2 -2
- package/src/shadow-objects.worker.js +0 -1
- package/src/shadow-objects.worker.js.map +2 -2
- package/src/shae-ent.js.map +1 -1
- package/src/shae-prop.js +2 -2
- package/src/shae-prop.js.map +2 -2
- package/src/shae-worker.js.map +1 -1
- package/src/types.d.ts +39 -8
- package/src/types.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.d.ts +45 -10
- package/src/utils/ConsoleLogger.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.js +54 -28
- package/src/utils/ConsoleLogger.js.map +2 -2
- package/src/utils/FrameLoop.d.ts +42 -3
- package/src/utils/FrameLoop.d.ts.map +1 -1
- package/src/utils/FrameLoop.js +71 -12
- package/src/utils/FrameLoop.js.map +2 -2
- package/src/utils/MicrotaskCollector.d.ts +45 -0
- package/src/utils/MicrotaskCollector.d.ts.map +1 -0
- package/src/utils/MicrotaskCollector.js +33 -0
- package/src/utils/MicrotaskCollector.js.map +7 -0
- package/src/utils/MicrotaskGate.d.ts +17 -0
- package/src/utils/MicrotaskGate.d.ts.map +1 -0
- package/src/utils/MicrotaskGate.js +20 -0
- package/src/utils/MicrotaskGate.js.map +7 -0
- package/src/utils/array-utils.js.map +1 -1
- package/src/utils/attr-utils.d.ts +6 -0
- package/src/utils/attr-utils.d.ts.map +1 -1
- package/src/utils/attr-utils.js +4 -2
- package/src/utils/attr-utils.js.map +2 -2
- package/src/utils/constants.js.map +1 -1
- package/src/utils/generateUUID.d.ts +12 -1
- package/src/utils/generateUUID.d.ts.map +1 -1
- package/src/utils/generateUUID.js +31 -266
- package/src/utils/generateUUID.js.map +2 -2
- package/src/utils/props-utils.d.ts +3 -2
- package/src/utils/props-utils.d.ts.map +1 -1
- package/src/utils/props-utils.js +13 -24
- package/src/utils/props-utils.js.map +2 -2
- package/src/utils/runGuarded.d.ts +27 -0
- package/src/utils/runGuarded.d.ts.map +1 -0
- package/src/utils/runGuarded.js +11 -0
- package/src/utils/runGuarded.js.map +7 -0
- package/src/utils/toMaybe.d.ts.map +1 -1
- package/src/utils/toMaybe.js.map +1 -1
- package/src/utils/toNamespace.d.ts.map +1 -1
- package/src/utils/toNamespace.js.map +1 -1
- package/src/utils/toUrlString.d.ts +12 -0
- package/src/utils/toUrlString.d.ts.map +1 -1
- package/src/utils/toUrlString.js.map +2 -2
- package/src/utils/waitForMessageOfType.d.ts +16 -1
- package/src/utils/waitForMessageOfType.d.ts.map +1 -1
- package/src/utils/waitForMessageOfType.js +28 -3
- package/src/utils/waitForMessageOfType.js.map +2 -2
- package/src/view/ComponentChanges.d.ts +91 -1
- package/src/view/ComponentChanges.d.ts.map +1 -1
- package/src/view/ComponentChanges.js +253 -69
- package/src/view/ComponentChanges.js.map +2 -2
- package/src/view/ComponentContext.d.ts +239 -7
- package/src/view/ComponentContext.d.ts.map +1 -1
- package/src/view/ComponentContext.js +648 -115
- package/src/view/ComponentContext.js.map +2 -2
- package/src/view/ComponentMemory.d.ts +3 -3
- package/src/view/ComponentMemory.d.ts.map +1 -1
- package/src/view/ComponentMemory.js +3 -1
- package/src/view/ComponentMemory.js.map +2 -2
- package/src/view/IShadowObjectEnvProxy.d.ts +33 -1
- package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.d.ts +2 -2
- package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.js +18 -11
- package/src/view/LocalShadowObjectEnv.js.map +2 -2
- package/src/view/RemoteWorkerEnv.d.ts +129 -3
- package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
- package/src/view/RemoteWorkerEnv.js +290 -60
- package/src/view/RemoteWorkerEnv.js.map +2 -2
- package/src/view/ShadowEnv.d.ts +73 -2
- package/src/view/ShadowEnv.d.ts.map +1 -1
- package/src/view/ShadowEnv.js +301 -64
- package/src/view/ShadowEnv.js.map +2 -2
- package/src/view/ViewComponent.d.ts +93 -8
- package/src/view/ViewComponent.d.ts.map +1 -1
- package/src/view/ViewComponent.js +148 -28
- package/src/view/ViewComponent.js.map +2 -2
- package/src/view/cloneChangeTrail.js.map +2 -2
- package/src/worker/MessageRouter.d.ts +14 -0
- package/src/worker/MessageRouter.d.ts.map +1 -1
- package/src/worker/MessageRouter.js +80 -18
- package/src/worker/MessageRouter.js.map +2 -2
- package/src/worker/WorkerRuntime.d.ts +22 -1
- package/src/worker/WorkerRuntime.d.ts.map +1 -1
- package/src/worker/WorkerRuntime.js +60 -8
- package/src/worker/WorkerRuntime.js.map +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteWorkerEnv.d.ts","sourceRoot":"","sources":["../../../src/view/RemoteWorkerEnv.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RemoteWorkerEnv.d.ts","sourceRoot":"","sources":["../../../src/view/RemoteWorkerEnv.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAA0B,eAAe,EAAoD,MAAM,aAAa,CAAC;AAC7H,OAAO,EAAiB,aAAa,EAAkD,MAAM,2BAA2B,CAAC;AAGzH,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,4BAA4B,CAAC;AA8CtE;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,sHAAsH;IACtH,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAAC;IAExC,YAAY,IAAI,EAAE,OAAO,GAAG,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAIlF;CACF;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAO,SAA8C,EAGhE;CACF;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAGpD;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;AAyC7D,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,GAAG,EAAE,eAAe,CAAC;IACrB,+BAA+B;IAC/B,IAAI,EAAE,OAAO,GAAG,cAAc,CAAC;IAC/B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,MAAM,EAAE,iBAAiB,CAAC;IAC1B,iDAAiD;IACjD,KAAK,EAAE,UAAU,GAAG,YAAY,CAAC;CAClC;AAED,qBAAa,eAAgB,YAAW,qBAAqB;;IAC3D,MAAM,CAAC,YAAY,SAAkB;IACrC,MAAM,CAAC,YAAY,SAAkB;IAgBrC,mDAAmD;IACnD,IAAI,MAAM,IAAI,aAAa,CAE1B;IAID;;;;OAIG;IACH,IAAI,QAAQ,IAAI,QAAQ,CAAC,cAAc,CAAC,CAEvC;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;;;;;;;OAUG;IACH,IAAI,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC,CA4B3C;IAED,YAAY,OAAO,CAAC,EAAE,sBAAsB,EAO3C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA6D3B;IAED,gBAAgB,CAAC,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA6C1F;IAED,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB7C;IAED,OAAO,IAAI,IAAI,CA8Bd;IAED,OAAO,CAAC,QAAQ,CAAC,aAAa,CAE5B;IAEF,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAEnC;IAEF,OAAO,CAAC,mBAAmB;IAkC3B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAMlC;IAEF;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;CAoBzB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emitStrict, once, retain } from "@spearwolf/eventize";
|
|
2
|
+
import { ChangeTrailRefusedError } from "../ChangeTrailRefusedError.js";
|
|
2
3
|
import {
|
|
3
4
|
AppliedChangeTrail,
|
|
4
5
|
ChangeTrail,
|
|
@@ -14,121 +15,325 @@ import {
|
|
|
14
15
|
WorkerLoadTimeout
|
|
15
16
|
} from "../constants.js";
|
|
16
17
|
import createWorker from "../create-worker.js";
|
|
17
|
-
import { CONSOLE_LOGGER, ConsoleLogger } from "../utils/ConsoleLogger.js";
|
|
18
|
+
import { CONSOLE_LOGGER, ConsoleLogger, consoleLoggerConfigKey, loadConsoleLoggerConfig } from "../utils/ConsoleLogger.js";
|
|
18
19
|
import { toUrlString } from "../utils/toUrlString.js";
|
|
19
|
-
import { waitForMessageOfType } from "../utils/waitForMessageOfType.js";
|
|
20
|
-
const
|
|
20
|
+
import { isTimeout, MaxWorkerTimeout, waitForMessageOfType } from "../utils/waitForMessageOfType.js";
|
|
21
|
+
const splitTransferables = (changeTrail) => {
|
|
22
|
+
if (!Array.isArray(changeTrail)) return { changeTrail };
|
|
23
|
+
let outbound;
|
|
21
24
|
let transferables;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
for (let i = 0; i < changeTrail.length; i++) {
|
|
26
|
+
const changeItem = changeTrail[i];
|
|
27
|
+
if (changeItem?.transferables) {
|
|
28
|
+
transferables = transferables ? [...transferables, ...changeItem.transferables] : [...changeItem.transferables];
|
|
29
|
+
outbound ??= [...changeTrail];
|
|
30
|
+
const withoutTransferables = { ...changeItem };
|
|
31
|
+
delete withoutTransferables.transferables;
|
|
32
|
+
outbound[i] = withoutTransferables;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { changeTrail: outbound ?? changeTrail, transferables };
|
|
36
|
+
};
|
|
37
|
+
class WorkerFailedError extends Error {
|
|
38
|
+
constructor(type, message, options) {
|
|
39
|
+
super(message, options);
|
|
40
|
+
this.name = "WorkerFailedError";
|
|
41
|
+
this.type = type;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
class WorkerDestroyedError extends Error {
|
|
45
|
+
constructor(message = "the worker environment has been destroyed") {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "WorkerDestroyedError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class WorkerReportedError extends Error {
|
|
51
|
+
constructor(name, message) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = name || "Error";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const DefaultWorkerTimeouts = {
|
|
57
|
+
loadTimeout: WorkerLoadTimeout,
|
|
58
|
+
configureTimeout: WorkerConfigureTimeout,
|
|
59
|
+
changeTrailTimeout: WorkerChangeTrailTimeout,
|
|
60
|
+
destroyTimeout: WorkerDestroyTimeout
|
|
61
|
+
};
|
|
62
|
+
const resolveTimeouts = (options, logger) => {
|
|
63
|
+
const resolved = { ...DefaultWorkerTimeouts };
|
|
64
|
+
if (options == null) return resolved;
|
|
65
|
+
for (const key of Object.keys(DefaultWorkerTimeouts)) {
|
|
66
|
+
if (!Object.hasOwn(options, key)) continue;
|
|
67
|
+
const value = options[key];
|
|
68
|
+
if (value === void 0) continue;
|
|
69
|
+
if (isTimeout(value)) {
|
|
70
|
+
resolved[key] = value;
|
|
71
|
+
} else {
|
|
72
|
+
logger.error(`ignoring the ${key} option: expected a number of milliseconds from 1 to ${MaxWorkerTimeout}, got`, value);
|
|
32
73
|
}
|
|
33
74
|
}
|
|
34
|
-
return
|
|
75
|
+
return resolved;
|
|
35
76
|
};
|
|
36
77
|
class RemoteWorkerEnv {
|
|
37
|
-
constructor() {
|
|
78
|
+
constructor(options) {
|
|
38
79
|
this.#isDestroyed = false;
|
|
39
80
|
this.#changeTrailSerial = 0;
|
|
40
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Aborted exactly once, with the error that ends this environment: a {@link WorkerFailedError}
|
|
83
|
+
* when the worker broke down, a {@link WorkerDestroyedError} when it was torn down. Whichever
|
|
84
|
+
* comes first keeps the signal, so a teardown never buries the failure that provoked it.
|
|
85
|
+
* It settles every request already waiting for a reply and turns away every later one.
|
|
86
|
+
*/
|
|
87
|
+
this.#workerFailure = new AbortController();
|
|
88
|
+
this.#logger = new ConsoleLogger("RemoteWorkerEnv");
|
|
89
|
+
this.onWorkerError = (event) => {
|
|
90
|
+
this.handleWorkerFailure("error", event, event.message || "the worker reported an error");
|
|
91
|
+
};
|
|
92
|
+
this.onWorkerMessageError = (event) => {
|
|
93
|
+
this.handleWorkerFailure("messageerror", event, "the worker sent a message that could not be deserialized");
|
|
94
|
+
};
|
|
95
|
+
this.onMessageFromWorker = (event) => {
|
|
96
|
+
if (event.data?.type === MessageToView) {
|
|
97
|
+
this.onMessageToView?.(event.data.data);
|
|
98
|
+
} else {
|
|
99
|
+
this.logger.debug("message from worker", event);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
this.#timeouts = Object.freeze(resolveTimeouts(options, this.logger));
|
|
41
103
|
retain(this, RemoteWorkerEnv.WorkerLoaded);
|
|
104
|
+
retain(this, RemoteWorkerEnv.WorkerFailed);
|
|
42
105
|
}
|
|
43
106
|
static {
|
|
44
107
|
this.WorkerLoaded = "workerLoaded";
|
|
45
108
|
}
|
|
109
|
+
static {
|
|
110
|
+
this.WorkerFailed = "workerFailed";
|
|
111
|
+
}
|
|
46
112
|
#worker;
|
|
47
113
|
#isDestroyed;
|
|
48
114
|
#changeTrailSerial;
|
|
115
|
+
#workerFailure;
|
|
116
|
+
#logger;
|
|
117
|
+
/** The logger this environment reports through. */
|
|
118
|
+
get logger() {
|
|
119
|
+
return this.#logger;
|
|
120
|
+
}
|
|
121
|
+
#timeouts;
|
|
122
|
+
/**
|
|
123
|
+
* The four timeouts this environment holds itself to, resolved once when it is built. The
|
|
124
|
+
* object is frozen and the slot holds no setter, so the constructor is the one way in --
|
|
125
|
+
* and `resolveTimeouts()` vets every value that goes through it.
|
|
126
|
+
*/
|
|
127
|
+
get timeouts() {
|
|
128
|
+
return this.#timeouts;
|
|
129
|
+
}
|
|
49
130
|
get isDestroyed() {
|
|
50
131
|
return this.#isDestroyed;
|
|
51
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolves once the worker is up.
|
|
135
|
+
*
|
|
136
|
+
* Every read hands out a promise that can reject, so a caller that only wants to
|
|
137
|
+
* kick the environment off and never awaits the result should still attach a
|
|
138
|
+
* `catch()` — otherwise a worker failure surfaces as an unhandled rejection.
|
|
139
|
+
*
|
|
140
|
+
* @throws {WorkerFailedError} if the worker fails before or after that happens
|
|
141
|
+
* @throws {WorkerDestroyedError} once the environment has been torn down — including every
|
|
142
|
+
* read after a load that had already succeeded, because there is no environment left to hand out
|
|
143
|
+
*/
|
|
52
144
|
get workerLoaded() {
|
|
53
|
-
|
|
145
|
+
const { signal } = this.#workerFailure;
|
|
146
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
let unsubscribeLoaded;
|
|
149
|
+
let settled = false;
|
|
150
|
+
const onFailure = () => {
|
|
151
|
+
if (settled) return;
|
|
152
|
+
settled = true;
|
|
153
|
+
unsubscribeLoaded?.();
|
|
154
|
+
reject(signal.reason);
|
|
155
|
+
};
|
|
156
|
+
unsubscribeLoaded = once(this, RemoteWorkerEnv.WorkerLoaded, (env) => {
|
|
157
|
+
if (settled) return;
|
|
158
|
+
settled = true;
|
|
159
|
+
signal.removeEventListener("abort", onFailure);
|
|
160
|
+
resolve(env);
|
|
161
|
+
});
|
|
162
|
+
if (settled) return;
|
|
163
|
+
signal.addEventListener("abort", onFailure, { once: true });
|
|
164
|
+
});
|
|
54
165
|
}
|
|
55
166
|
async start() {
|
|
167
|
+
const { signal } = this.#workerFailure;
|
|
168
|
+
if (signal.aborted) throw signal.reason;
|
|
56
169
|
if (this.#worker) {
|
|
57
|
-
|
|
58
|
-
this.logger.warn("already started");
|
|
59
|
-
}
|
|
170
|
+
this.logger.warn("already started");
|
|
60
171
|
return this.workerLoaded.then(() => {
|
|
61
172
|
if (this.isDestroyed) {
|
|
62
|
-
throw
|
|
173
|
+
throw new WorkerDestroyedError();
|
|
63
174
|
}
|
|
64
|
-
return void 0;
|
|
65
175
|
});
|
|
66
176
|
}
|
|
67
177
|
const worker = this.#worker = createWorker();
|
|
178
|
+
worker.addEventListener("error", this.onWorkerError);
|
|
179
|
+
worker.addEventListener("messageerror", this.onWorkerMessageError);
|
|
68
180
|
this.configureConsoleLogger(worker);
|
|
69
181
|
try {
|
|
70
|
-
await waitForMessageOfType(worker, Loaded,
|
|
182
|
+
await waitForMessageOfType(worker, Loaded, this.timeouts.loadTimeout, void 0, signal);
|
|
71
183
|
if (this.isDestroyed) {
|
|
72
|
-
throw
|
|
184
|
+
throw signal.aborted ? signal.reason : new WorkerDestroyedError();
|
|
73
185
|
}
|
|
74
|
-
worker.addEventListener("message", this.onMessageFromWorker
|
|
186
|
+
worker.addEventListener("message", this.onMessageFromWorker);
|
|
75
187
|
queueMicrotask(() => {
|
|
76
|
-
|
|
188
|
+
this.announceLoaded();
|
|
77
189
|
});
|
|
78
190
|
} catch (error) {
|
|
79
|
-
|
|
191
|
+
if (error !== signal.reason) {
|
|
192
|
+
this.logger.error("failed to start", error);
|
|
193
|
+
}
|
|
194
|
+
this.stopListeningTo(worker);
|
|
80
195
|
this.#worker = void 0;
|
|
196
|
+
if (!this.#isDestroyed) {
|
|
197
|
+
worker.terminate();
|
|
198
|
+
}
|
|
81
199
|
throw error;
|
|
82
200
|
}
|
|
83
201
|
}
|
|
84
202
|
applyChangeTrail(changeTrail, waitForConfirmation) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return waitForMessageOfType(this.#worker, AppliedChangeTrail, WorkerChangeTrailTimeout, (data) => {
|
|
94
|
-
if (data.error) throw data.error;
|
|
95
|
-
return data.serial === serial;
|
|
96
|
-
});
|
|
97
|
-
} else {
|
|
203
|
+
const { signal } = this.#workerFailure;
|
|
204
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
205
|
+
const worker = this.#worker;
|
|
206
|
+
if (worker == null) return Promise.reject(new WorkerDestroyedError());
|
|
207
|
+
const { changeTrail: outbound, transferables } = splitTransferables(changeTrail);
|
|
208
|
+
const message = { type: ChangeTrail, changeTrail: outbound };
|
|
209
|
+
if (!waitForConfirmation) {
|
|
210
|
+
worker.postMessage(message, transferables ?? []);
|
|
98
211
|
return Promise.resolve();
|
|
99
212
|
}
|
|
213
|
+
const serial = ++this.#changeTrailSerial;
|
|
214
|
+
message.serial = serial;
|
|
215
|
+
worker.postMessage(message, transferables ?? []);
|
|
216
|
+
return waitForMessageOfType(
|
|
217
|
+
worker,
|
|
218
|
+
AppliedChangeTrail,
|
|
219
|
+
this.timeouts.changeTrailTimeout,
|
|
220
|
+
(data) => {
|
|
221
|
+
if (data.serial !== serial) return false;
|
|
222
|
+
if (data.error) {
|
|
223
|
+
const reason = new WorkerReportedError(data.errorName, data.error);
|
|
224
|
+
if (typeof data.appliedCount === "number") {
|
|
225
|
+
throw new ChangeTrailRefusedError(data.appliedCount, changeTrail.length, { cause: reason });
|
|
226
|
+
}
|
|
227
|
+
throw reason;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
},
|
|
231
|
+
signal
|
|
232
|
+
);
|
|
100
233
|
}
|
|
101
234
|
importScript(url) {
|
|
235
|
+
const { signal } = this.#workerFailure;
|
|
236
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
237
|
+
const worker = this.#worker;
|
|
238
|
+
if (worker == null) return Promise.reject(new WorkerDestroyedError());
|
|
102
239
|
url = toUrlString(url);
|
|
103
|
-
|
|
104
|
-
return waitForMessageOfType(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
240
|
+
worker.postMessage({ type: Configure, importModule: url });
|
|
241
|
+
return waitForMessageOfType(
|
|
242
|
+
worker,
|
|
243
|
+
ImportedModule,
|
|
244
|
+
this.timeouts.configureTimeout,
|
|
245
|
+
(data) => {
|
|
246
|
+
if (data.url !== url) return false;
|
|
247
|
+
if (data.error) throw new WorkerReportedError(data.errorName, data.error);
|
|
248
|
+
return true;
|
|
249
|
+
},
|
|
250
|
+
signal
|
|
251
|
+
);
|
|
108
252
|
}
|
|
109
253
|
destroy() {
|
|
110
|
-
|
|
254
|
+
this.#isDestroyed = true;
|
|
111
255
|
const worker = this.#worker;
|
|
112
256
|
this.#worker = void 0;
|
|
113
|
-
this.#
|
|
257
|
+
this.#workerFailure.abort(new WorkerDestroyedError());
|
|
258
|
+
if (worker == null) return;
|
|
259
|
+
this.stopListeningTo(worker);
|
|
114
260
|
worker.postMessage({ type: Destroy });
|
|
115
|
-
waitForMessageOfType(worker, Destroyed,
|
|
261
|
+
waitForMessageOfType(worker, Destroyed, this.timeouts.destroyTimeout).catch((error) => {
|
|
262
|
+
this.logger.warn("the worker did not acknowledge the teardown", error);
|
|
263
|
+
}).finally(() => {
|
|
116
264
|
worker.terminate();
|
|
117
265
|
});
|
|
118
266
|
}
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
267
|
+
handleWorkerFailure(type, event, message) {
|
|
268
|
+
if (this.#isDestroyed) return;
|
|
269
|
+
this.#isDestroyed = true;
|
|
270
|
+
const reason = new WorkerFailedError(type, message, {
|
|
271
|
+
cause: type === "error" ? event.error ?? event : event
|
|
272
|
+
});
|
|
273
|
+
this.logger.error(message, event);
|
|
274
|
+
const worker = this.#worker;
|
|
275
|
+
this.#worker = void 0;
|
|
276
|
+
if (worker != null) this.stopListeningTo(worker);
|
|
277
|
+
this.#workerFailure.abort(reason);
|
|
278
|
+
worker?.terminate();
|
|
279
|
+
try {
|
|
280
|
+
this.onProxyFailed?.(reason);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.logger.error("the proxy-failed callback threw", error);
|
|
124
283
|
}
|
|
284
|
+
this.announceFailure({ env: this, type, message, reason, event });
|
|
125
285
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Announces the completed handshake to the consumers.
|
|
288
|
+
*
|
|
289
|
+
* `emitStrict()` rather than `emit()`, and the retain policy is what decides it. `workerLoaded`
|
|
290
|
+
* reads the retained value, and eventize writes that value only once the dispatch has run
|
|
291
|
+
* through: under the plain dispatch a single listener that throws would take the replay for
|
|
292
|
+
* every later subscriber with it, and every later read of `workerLoaded` would wait for a
|
|
293
|
+
* failure or a teardown instead of resolving. The guarded dispatch serves every listener and
|
|
294
|
+
* writes the retained value, because the event was delivered.
|
|
295
|
+
*
|
|
296
|
+
* What the listeners threw arrives here afterwards -- one of them unchanged, several as an
|
|
297
|
+
* `AggregateError` in dispatch order -- and is reported. It goes no further: this runs from a
|
|
298
|
+
* microtask, where a throw has no caller left to reach and would surface as an unhandled error.
|
|
299
|
+
*/
|
|
300
|
+
announceLoaded() {
|
|
301
|
+
try {
|
|
302
|
+
emitStrict(this, RemoteWorkerEnv.WorkerLoaded, this);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
this.logger.error("a workerLoaded listener threw", error);
|
|
131
305
|
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Announces the failure to the consumers. The same guarded dispatch as
|
|
309
|
+
* {@link RemoteWorkerEnv.announceLoaded}, for the same reason: `WorkerFailed` is documented as
|
|
310
|
+
* retained, and a consumer that subscribes only after the failure has to be able to hear about
|
|
311
|
+
* it. Every listener is served, the retained value is written, and whatever was thrown is
|
|
312
|
+
* reported here and reaches no caller.
|
|
313
|
+
*/
|
|
314
|
+
announceFailure(payload) {
|
|
315
|
+
try {
|
|
316
|
+
emitStrict(this, RemoteWorkerEnv.WorkerFailed, payload);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
this.logger.error("a workerFailed listener threw", error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Takes every listener of this environment off a worker. Whoever registered them takes them
|
|
323
|
+
* off again -- between the teardown and the `terminate()` that ends it the worker stays
|
|
324
|
+
* alive, and through its listeners it keeps this environment and everything it references
|
|
325
|
+
* reachable for exactly that long.
|
|
326
|
+
*/
|
|
327
|
+
stopListeningTo(worker) {
|
|
328
|
+
worker.removeEventListener("error", this.onWorkerError);
|
|
329
|
+
worker.removeEventListener("messageerror", this.onWorkerMessageError);
|
|
330
|
+
worker.removeEventListener("message", this.onMessageFromWorker);
|
|
331
|
+
}
|
|
332
|
+
configureConsoleLogger(worker) {
|
|
333
|
+
const configKey = ["RemoteWorkerEnv", "workerConfig"];
|
|
334
|
+
const storageKey = consoleLoggerConfigKey(configKey);
|
|
335
|
+
const workerConfig = this.readWorkerConfig(configKey, storageKey);
|
|
336
|
+
this.logger.info("load console-logger worker config", { storageKey, workerConfig });
|
|
132
337
|
worker.postMessage({
|
|
133
338
|
type: CONSOLE_LOGGER,
|
|
134
339
|
config: {
|
|
@@ -139,8 +344,33 @@ class RemoteWorkerEnv {
|
|
|
139
344
|
}
|
|
140
345
|
});
|
|
141
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* The console-logger config for the worker, read from the storage key this environment
|
|
349
|
+
* announces. The key is a diagnostic switch, set by hand and sharing its namespace with
|
|
350
|
+
* everything else on this origin, so its content decides nothing about whether the worker
|
|
351
|
+
* comes up: anything that is not a plain JSON object counts as no config at all, exactly
|
|
352
|
+
* like an absent key.
|
|
353
|
+
*/
|
|
354
|
+
readWorkerConfig(configKey, storageKey) {
|
|
355
|
+
const stored = loadConsoleLoggerConfig(configKey, "{}");
|
|
356
|
+
let parsed;
|
|
357
|
+
try {
|
|
358
|
+
parsed = JSON.parse(stored);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
this.logger.error(`ignoring the unreadable console-logger worker config at "${storageKey}"`, stored, error);
|
|
361
|
+
return {};
|
|
362
|
+
}
|
|
363
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
364
|
+
this.logger.error(`ignoring the console-logger worker config at "${storageKey}": expected a JSON object`, stored);
|
|
365
|
+
return {};
|
|
366
|
+
}
|
|
367
|
+
return parsed;
|
|
368
|
+
}
|
|
142
369
|
}
|
|
143
370
|
export {
|
|
144
|
-
RemoteWorkerEnv
|
|
371
|
+
RemoteWorkerEnv,
|
|
372
|
+
WorkerDestroyedError,
|
|
373
|
+
WorkerFailedError,
|
|
374
|
+
WorkerReportedError
|
|
145
375
|
};
|
|
146
376
|
//# sourceMappingURL=RemoteWorkerEnv.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/view/RemoteWorkerEnv.ts"],
|
|
4
|
-
"sourcesContent": ["import {emit, onceAsync, retain} from '@spearwolf/eventize';\nimport {\n AppliedChangeTrail,\n ChangeTrail,\n Configure,\n Destroy,\n Destroyed,\n ImportedModule,\n Loaded,\n MessageToView,\n WorkerChangeTrailTimeout,\n WorkerConfigureTimeout,\n WorkerDestroyTimeout,\n WorkerLoadTimeout,\n} from '../constants.js';\nimport createWorker from '../create-worker.js';\nimport type {AppliedChangeTrailEvent, ChangeTrailType, ImportedModuleEvent, TransferablesType} from '../types.js';\nimport {CONSOLE_LOGGER, ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {toUrlString} from '../utils/toUrlString.js';\nimport {waitForMessageOfType} from '../utils/waitForMessageOfType.js';\nimport type {IShadowObjectEnvProxy} from './IShadowObjectEnvProxy.js';\n\nconst removeTransferables = (changeTrail: ChangeTrailType): TransferablesType | undefined => {\n let transferables: TransferablesType | undefined;\n\n if (changeTrail != null && Array.isArray(changeTrail)) {\n for (const changeItem of changeTrail) {\n if (changeItem.transferables) {\n if (!transferables) {\n transferables = changeItem.transferables;\n } else {\n transferables = [...transferables, ...changeItem.transferables];\n }\n delete changeItem.transferables;\n }\n }\n }\n\n return transferables;\n};\n\nexport class RemoteWorkerEnv implements IShadowObjectEnvProxy {\n static WorkerLoaded = 'workerLoaded';\n\n #worker?: Worker;\n #isDestroyed = false;\n #changeTrailSerial = 0;\n\n readonly logger = new ConsoleLogger('RemoteWorkerEnv');\n\n get isDestroyed(): boolean {\n return this.#isDestroyed;\n }\n\n get workerLoaded(): Promise<RemoteWorkerEnv> {\n return onceAsync<RemoteWorkerEnv>(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerLoaded);\n }\n\n constructor() {\n retain(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerLoaded);\n }\n\n async start(): Promise<void> {\n if (this.#worker) {\n if (this.logger.isWarn) {\n this.logger.warn('already started');\n }\n\n return this.workerLoaded.then((): void => {\n if (this.isDestroyed) {\n throw 'worker was destroyed';\n }\n return undefined;\n });\n }\n\n const worker = (this.#worker = createWorker());\n\n this.configureConsoleLogger(worker);\n\n try {\n await waitForMessageOfType(worker, Loaded, WorkerLoadTimeout);\n\n if (this.isDestroyed) {\n throw 'worker was destroyed';\n }\n\n worker.addEventListener('message', this.onMessageFromWorker.bind(this));\n\n queueMicrotask(() => {\n emit(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerLoaded, this);\n });\n } catch (error) {\n this.logger.error('failed to start', error);\n this.#worker = undefined;\n throw error;\n }\n }\n\n applyChangeTrail(changeTrail: ChangeTrailType, waitForConfirmation: boolean): Promise<void> {\n const transferables = removeTransferables(changeTrail);\n const message = {type: ChangeTrail, changeTrail} as any;\n\n const serial = ++this.#changeTrailSerial;\n if (waitForConfirmation) {\n message.serial = serial;\n }\n\n this.#worker.postMessage(message, transferables);\n\n if (waitForConfirmation) {\n return waitForMessageOfType(this.#worker, AppliedChangeTrail, WorkerChangeTrailTimeout, (data: AppliedChangeTrailEvent) => {\n if (data.error) throw data.error;\n return data.serial === serial;\n });\n } else {\n return Promise.resolve();\n }\n }\n\n importScript(url: URL | string): Promise<void> {\n url = toUrlString(url);\n this.#worker.postMessage({type: Configure, importModule: url});\n return waitForMessageOfType(this.#worker, ImportedModule, WorkerConfigureTimeout, (data: ImportedModuleEvent) => {\n if (data.error) throw data.error;\n return data.url === url;\n });\n }\n\n destroy(): void {\n if (!this.#worker) return;\n\n const worker = this.#worker;\n this.#worker = undefined;\n\n this.#isDestroyed = true;\n\n worker.postMessage({type: Destroy});\n\n waitForMessageOfType(worker, Destroyed, WorkerDestroyTimeout).finally(() => {\n worker.terminate();\n });\n }\n\n private onMessageFromWorker(event: MessageEvent) {\n if (event.data?.type === MessageToView) {\n (this as IShadowObjectEnvProxy).onMessageToView?.(event.data.data);\n } else if (this.logger.isDebug) {\n this.logger.debug('message from worker', event);\n }\n }\n\n private configureConsoleLogger(worker: Worker) {\n const workerConfigKey = `${CONSOLE_LOGGER}.RemoteWorkerEnv.workerConfig`;\n const workerConfig = JSON.parse(localStorage.getItem(workerConfigKey) ?? '{}');\n\n if (this.logger.isInfo) {\n this.logger.info('load console-logger worker config', {localStorageKey: workerConfigKey, workerConfig});\n }\n\n worker.postMessage({\n type: CONSOLE_LOGGER,\n config: {\n ...ConsoleLogger.sharedConfig,\n enable: this.logger.isEnabled,\n ...workerConfig,\n ...(ConsoleLogger.isEnabled ? {} : {enable: false}),\n },\n });\n }\n}"],
|
|
5
|
-
"mappings": "AAAA,SAAQ,MAAM,
|
|
4
|
+
"sourcesContent": ["import {emitStrict, once, retain} from '@spearwolf/eventize';\nimport {ChangeTrailRefusedError} from '../ChangeTrailRefusedError.js';\nimport {\n AppliedChangeTrail,\n ChangeTrail,\n Configure,\n Destroy,\n Destroyed,\n ImportedModule,\n Loaded,\n MessageToView,\n WorkerChangeTrailTimeout,\n WorkerConfigureTimeout,\n WorkerDestroyTimeout,\n WorkerLoadTimeout,\n} from '../constants.js';\nimport createWorker from '../create-worker.js';\nimport type {AppliedChangeTrailEvent, ChangeTrailType, ImportedModuleEvent, SyncEvent, TransferablesType} from '../types.js';\nimport {CONSOLE_LOGGER, ConsoleLogger, consoleLoggerConfigKey, loadConsoleLoggerConfig} from '../utils/ConsoleLogger.js';\nimport {toUrlString} from '../utils/toUrlString.js';\nimport {isTimeout, MaxWorkerTimeout, waitForMessageOfType} from '../utils/waitForMessageOfType.js';\nimport type {IShadowObjectEnvProxy} from './IShadowObjectEnvProxy.js';\n\n/**\n * Splits the transferables out of a change trail without writing to it. The trail handed in is a\n * snapshot that travels on after this call -- to the `ShadowEnv.AfterSync` consumers when the\n * environment applies it, to the `ShadowEnv.SyncFailed` consumers when it refuses it -- so an\n * entry carrying transferables is replaced by a shallow copy without them and every other\n * entry is passed through as it is. A trail that carries none is handed back object for\n * object.\n */\nconst splitTransferables = (\n changeTrail: ChangeTrailType,\n): {changeTrail: ChangeTrailType; transferables?: TransferablesType | undefined} => {\n if (!Array.isArray(changeTrail)) return {changeTrail};\n\n let outbound: ChangeTrailType | undefined;\n let transferables: TransferablesType | undefined;\n\n for (let i = 0; i < changeTrail.length; i++) {\n const changeItem = changeTrail[i];\n if (changeItem?.transferables) {\n transferables = transferables ? [...transferables, ...changeItem.transferables] : [...changeItem.transferables];\n\n outbound ??= [...changeTrail];\n const withoutTransferables = {...changeItem};\n delete withoutTransferables.transferables;\n outbound[i] = withoutTransferables;\n }\n }\n\n return {changeTrail: outbound ?? changeTrail, transferables};\n};\n\n/**\n * What actually goes on the wire for a `ChangeTrail` message. `SyncEvent` describes the shape a\n * change trail carries once it is inside the worker, without the `type` discriminant that gets\n * it there \u2014 its two neighbors on the wire (`ImportedModuleEvent`, `AppliedChangeTrailEvent`)\n * both require that field, so adding it to `SyncEvent` itself would either make it mandatory\n * where `LocalShadowObjectEnv` never sets it, or optional on a public type whose neighbors need\n * it required. Kept local instead: it says exactly what this message is without touching the\n * exported type surface.\n */\ninterface ChangeTrailMessage extends SyncEvent {\n type: typeof ChangeTrail;\n}\n\n/**\n * The reason every pending and every later worker request is rejected with\n * once the worker has failed.\n */\nexport class WorkerFailedError extends Error {\n /** `'error'` when the worker itself threw, `'messageerror'` when it sent something that could not be deserialized. */\n readonly type: 'error' | 'messageerror';\n\n constructor(type: 'error' | 'messageerror', message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'WorkerFailedError';\n this.type = type;\n }\n}\n\n/**\n * The reason a request is rejected with once the environment has been torn down\n * with {@link RemoteWorkerEnv.destroy}. The worker is gone; no reply can arrive.\n */\nexport class WorkerDestroyedError extends Error {\n constructor(message = 'the worker environment has been destroyed') {\n super(message);\n this.name = 'WorkerDestroyedError';\n }\n}\n\n/**\n * The reason a request is rejected with when the worker reported a failure of its own -- a\n * module that would not import, a change trail its Kernel refused. An error does not survive\n * structured cloning as the object it is, so the two fields that do are rebuilt here, and\n * `name` is the name the error called itself inside the worker: a caller reads it the same\n * way it reads the name of an error a `LocalShadowObjectEnv` hands it. A name that is missing\n * or empty names no class, and `Error` is what such a reason is called -- the same name a\n * plain `new Error(message)` carries on the local side.\n *\n * What does not come across is the class and everything it added. `instanceof EntityUuidInUseError`\n * is `false` here and there is no `uuid` field, whatever `name` says; `instanceof WorkerReportedError`\n * is what tells such a reason apart from one raised on this side.\n */\nexport class WorkerReportedError extends Error {\n constructor(name: string | undefined, message: string) {\n super(message);\n this.name = name || 'Error';\n }\n}\n\n/**\n * How long a {@link RemoteWorkerEnv} waits for each of the four replies a worker owes it,\n * in milliseconds.\n */\nexport interface WorkerTimeouts {\n /** the `Loaded` handshake at the start */\n loadTimeout: number;\n /** the `ImportedModule` reply to an `importScript()` */\n configureTimeout: number;\n /** the `AppliedChangeTrail` confirmation of a change trail sent with `waitForConfirmation` */\n changeTrailTimeout: number;\n /** the `Destroyed` acknowledgement of a teardown */\n destroyTimeout: number;\n}\n\n/**\n * What {@link RemoteWorkerEnv} takes. Every value left out keeps its default \u2014 the four\n * `Worker*Timeout` constants. A value that is not a number of milliseconds from 1 to\n * 2147483647 \u2014 close to 25 days, the longest delay a timer keeps \u2014 is reported through the\n * logger and the default applies.\n */\nexport type RemoteWorkerEnvOptions = Partial<WorkerTimeouts>;\n\nconst DefaultWorkerTimeouts: WorkerTimeouts = {\n loadTimeout: WorkerLoadTimeout,\n configureTimeout: WorkerConfigureTimeout,\n changeTrailTimeout: WorkerChangeTrailTimeout,\n destroyTimeout: WorkerDestroyTimeout,\n};\n\n/**\n * Resolves the options against the defaults. A timeout is a number of milliseconds from 1 to\n * {@link MaxWorkerTimeout}; anything else is refused and reported, and the default stays.\n *\n * Zero and Infinity are refused along with the rest, and that is the point of the rule rather\n * than an oversight: `waitForMessageOfType()` arms no timer for either of them, so a teardown\n * given one of the two would wait for an acknowledgement a dead worker never sends \u2014 and the\n * `terminate()` that ends the teardown hangs on that very chain. One rule for all four values\n * is easier to hold on to than three that allow it and one that does not, and {@link MaxWorkerTimeout}\n * \u2014 close to 25 days \u2014 is as long as a wait can honestly be made.\n */\nconst resolveTimeouts = (options: RemoteWorkerEnvOptions | undefined, logger: ConsoleLogger): WorkerTimeouts => {\n const resolved: WorkerTimeouts = {...DefaultWorkerTimeouts};\n if (options == null) return resolved;\n\n for (const key of Object.keys(DefaultWorkerTimeouts) as (keyof WorkerTimeouts)[]) {\n // own keys only: what an object inherits from its prototype is not what its author wrote down\n if (!Object.hasOwn(options, key)) continue;\n\n const value = options[key];\n if (value === undefined) continue;\n\n if (isTimeout(value)) {\n resolved[key] = value;\n } else {\n logger.error(`ignoring the ${key} option: expected a number of milliseconds from 1 to ${MaxWorkerTimeout}, got`, value);\n }\n }\n\n return resolved;\n};\n\n/** What {@link RemoteWorkerEnv.WorkerFailed} carries. */\nexport interface WorkerFailedEvent {\n /** the environment whose worker failed */\n env: RemoteWorkerEnv;\n /** what the worker reported */\n type: 'error' | 'messageerror';\n /** a readable description of the failure */\n message: string;\n /** the error every pending and every later request is rejected with */\n reason: WorkerFailedError;\n /** the worker event the failure was read from */\n event: ErrorEvent | MessageEvent;\n}\n\nexport class RemoteWorkerEnv implements IShadowObjectEnvProxy {\n static WorkerLoaded = 'workerLoaded';\n static WorkerFailed = 'workerFailed';\n\n #worker?: Worker | undefined;\n #isDestroyed = false;\n #changeTrailSerial = 0;\n\n /**\n * Aborted exactly once, with the error that ends this environment: a {@link WorkerFailedError}\n * when the worker broke down, a {@link WorkerDestroyedError} when it was torn down. Whichever\n * comes first keeps the signal, so a teardown never buries the failure that provoked it.\n * It settles every request already waiting for a reply and turns away every later one.\n */\n readonly #workerFailure = new AbortController();\n\n readonly #logger = new ConsoleLogger('RemoteWorkerEnv');\n\n /** The logger this environment reports through. */\n get logger(): ConsoleLogger {\n return this.#logger;\n }\n\n readonly #timeouts: Readonly<WorkerTimeouts>;\n\n /**\n * The four timeouts this environment holds itself to, resolved once when it is built. The\n * object is frozen and the slot holds no setter, so the constructor is the one way in --\n * and `resolveTimeouts()` vets every value that goes through it.\n */\n get timeouts(): Readonly<WorkerTimeouts> {\n return this.#timeouts;\n }\n\n get isDestroyed(): boolean {\n return this.#isDestroyed;\n }\n\n /**\n * Resolves once the worker is up.\n *\n * Every read hands out a promise that can reject, so a caller that only wants to\n * kick the environment off and never awaits the result should still attach a\n * `catch()` \u2014 otherwise a worker failure surfaces as an unhandled rejection.\n *\n * @throws {WorkerFailedError} if the worker fails before or after that happens\n * @throws {WorkerDestroyedError} once the environment has been torn down \u2014 including every\n * read after a load that had already succeeded, because there is no environment left to hand out\n */\n get workerLoaded(): Promise<RemoteWorkerEnv> {\n const {signal} = this.#workerFailure;\n if (signal.aborted) return Promise.reject(signal.reason);\n\n return new Promise<RemoteWorkerEnv>((resolve, reject) => {\n let unsubscribeLoaded: (() => void) | undefined;\n let settled = false;\n\n const onFailure = () => {\n if (settled) return;\n settled = true;\n unsubscribeLoaded?.();\n reject(signal.reason);\n };\n\n unsubscribeLoaded = once(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerLoaded, (env: RemoteWorkerEnv) => {\n if (settled) return;\n settled = true;\n signal.removeEventListener('abort', onFailure);\n resolve(env);\n });\n\n // a retained WorkerLoaded is replayed inside once(), before it returns \u2014\n // then there is nothing left to wait for and no abort listener to attach\n if (settled) return;\n\n signal.addEventListener('abort', onFailure, {once: true});\n });\n }\n\n constructor(options?: RemoteWorkerEnvOptions) {\n // the logger is a field initializer and is therefore already in place\n this.#timeouts = Object.freeze(resolveTimeouts(options, this.logger));\n\n retain(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerLoaded);\n // a consumer that subscribes only after the failure still gets to hear about it\n retain(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerFailed);\n }\n\n async start(): Promise<void> {\n // both endings of this environment abort the signal, and it keeps the reason of whichever\n // came first. A finished environment must not spawn a worker: nothing would ever reach it,\n // and nothing would ever shut it down again\n const {signal} = this.#workerFailure;\n if (signal.aborted) throw signal.reason;\n\n if (this.#worker) {\n this.logger.warn('already started');\n\n return this.workerLoaded.then((): void => {\n if (this.isDestroyed) {\n throw new WorkerDestroyedError();\n }\n });\n }\n\n const worker = (this.#worker = createWorker());\n\n // registered before the load handshake begins: a worker that dies while it is\n // still coming up must not leave the caller waiting for the load timeout\n worker.addEventListener('error', this.onWorkerError);\n worker.addEventListener('messageerror', this.onWorkerMessageError);\n\n this.configureConsoleLogger(worker);\n\n try {\n await waitForMessageOfType(worker, Loaded, this.timeouts.loadTimeout, undefined, signal);\n\n // the handshake completed but the environment ended while its reply was on the way here.\n // The reason of that ending is what the caller gets \u2014 a fresh error of our own would read\n // as a start that went wrong, and the catch below would report it as one\n if (this.isDestroyed) {\n throw signal.aborted ? signal.reason : new WorkerDestroyedError();\n }\n\n worker.addEventListener('message', this.onMessageFromWorker);\n\n queueMicrotask(() => {\n this.announceLoaded();\n });\n } catch (error) {\n // the failure path reports with the worker event in hand; anything else is on us\n if (error !== signal.reason) {\n this.logger.error('failed to start', error);\n }\n\n this.stopListeningTo(worker);\n this.#worker = undefined;\n\n // only a start that nobody else is unwinding owns this worker: the reference is gone,\n // nobody can reach it any more, and leaving it running would keep a thread open for an\n // environment that never started. A failure has already terminated it, and a teardown\n // in flight is waiting for its Destroyed reply \u2014 cutting that short here would leave\n // destroy() to sit out its timeout and skip the worker-side teardown entirely.\n if (!this.#isDestroyed) {\n worker.terminate();\n }\n\n throw error;\n }\n }\n\n applyChangeTrail(changeTrail: ChangeTrailType, waitForConfirmation: boolean): Promise<void> {\n const {signal} = this.#workerFailure;\n if (signal.aborted) return Promise.reject(signal.reason);\n\n const worker = this.#worker;\n if (worker == null) return Promise.reject(new WorkerDestroyedError());\n\n const {changeTrail: outbound, transferables} = splitTransferables(changeTrail);\n const message: ChangeTrailMessage = {type: ChangeTrail, changeTrail: outbound};\n\n if (!waitForConfirmation) {\n worker.postMessage(message, transferables ?? []);\n return Promise.resolve();\n }\n\n // the counter moves only where a number actually goes on the wire: it is the sole link\n // between a request and its confirmation, and a sequence with invisible jumps in it tells\n // a later diagnosis nothing\n const serial = ++this.#changeTrailSerial;\n message.serial = serial;\n\n worker.postMessage(message, transferables ?? []);\n\n return waitForMessageOfType(\n worker,\n AppliedChangeTrail,\n this.timeouts.changeTrailTimeout,\n (data: AppliedChangeTrailEvent) => {\n // the serial decides who a confirmation concerns -- an error belonging to another\n // trail would otherwise reject the request that happens to be waiting here\n if (data.serial !== serial) return false;\n if (data.error) {\n const reason = new WorkerReportedError(data.errorName, data.error);\n // Only a confirmation that names the count can move the line the view draws between\n // applied and pending; one that does not carries no more than the reason itself, and\n // is handed on as that reason.\n if (typeof data.appliedCount === 'number') {\n throw new ChangeTrailRefusedError(data.appliedCount, changeTrail.length, {cause: reason});\n }\n throw reason;\n }\n return true;\n },\n signal,\n );\n }\n\n importScript(url: URL | string): Promise<void> {\n const {signal} = this.#workerFailure;\n if (signal.aborted) return Promise.reject(signal.reason);\n\n const worker = this.#worker;\n if (worker == null) return Promise.reject(new WorkerDestroyedError());\n\n url = toUrlString(url);\n worker.postMessage({type: Configure, importModule: url});\n return waitForMessageOfType(\n worker,\n ImportedModule,\n this.timeouts.configureTimeout,\n (data: ImportedModuleEvent) => {\n if (data.url !== url) return false;\n if (data.error) throw new WorkerReportedError(data.errorName, data.error);\n return true;\n },\n signal,\n );\n }\n\n destroy(): void {\n // the teardown counts even when there is no worker to tear down: everything that comes\n // afterwards has to meet an environment that is gone\n this.#isDestroyed = true;\n\n // releasing the reference here is what makes the teardown idempotent: a repeated call finds\n // nothing left, sends no second Destroy and terminates nothing twice\n const worker = this.#worker;\n this.#worker = undefined;\n\n // settles everything still waiting for a reply: a start() in the middle of its load handshake\n // would otherwise sit out its load timeout, and workerLoaded would never hear anything again\n this.#workerFailure.abort(new WorkerDestroyedError());\n\n if (worker == null) return;\n\n this.stopListeningTo(worker);\n\n worker.postMessage({type: Destroy});\n\n waitForMessageOfType(worker, Destroyed, this.timeouts.destroyTimeout)\n .catch((error) => {\n // `.finally()` passes a rejection on, so without this the silence of a worker ends as an\n // unhandled rejection once the destroy timeout is up. It is terminated either way, and\n // by then there is nobody left to hand the error to\n this.logger.warn('the worker did not acknowledge the teardown', error);\n })\n .finally(() => {\n worker.terminate();\n });\n }\n\n private readonly onWorkerError = (event: ErrorEvent): void => {\n this.handleWorkerFailure('error', event, event.message || 'the worker reported an error');\n };\n\n private readonly onWorkerMessageError = (event: MessageEvent): void => {\n this.handleWorkerFailure('messageerror', event, 'the worker sent a message that could not be deserialized');\n };\n\n private handleWorkerFailure(type: 'error' | 'messageerror', event: ErrorEvent | MessageEvent, message: string): void {\n // a deliberate teardown, or a failure that was already reported: the first one wins\n if (this.#isDestroyed) return;\n\n this.#isDestroyed = true;\n\n const reason = new WorkerFailedError(type, message, {\n cause: type === 'error' ? ((event as ErrorEvent).error ?? event) : event,\n });\n\n this.logger.error(message, event);\n\n const worker = this.#worker;\n this.#worker = undefined;\n\n if (worker != null) this.stopListeningTo(worker);\n\n // settle everyone waiting for a reply before the worker goes away \u2014 it can no longer arrive\n this.#workerFailure.abort(reason);\n\n worker?.terminate();\n\n // the ShadowEnv is told first, so that the state it derives from this is already\n // settled when consumers hear about it. Whatever happens down that path stays\n // there \u2014 the public event is announced either way\n try {\n (this as IShadowObjectEnvProxy).onProxyFailed?.(reason);\n } catch (error) {\n this.logger.error('the proxy-failed callback threw', error);\n }\n\n this.announceFailure({env: this, type, message, reason, event});\n }\n\n /**\n * Announces the completed handshake to the consumers.\n *\n * `emitStrict()` rather than `emit()`, and the retain policy is what decides it. `workerLoaded`\n * reads the retained value, and eventize writes that value only once the dispatch has run\n * through: under the plain dispatch a single listener that throws would take the replay for\n * every later subscriber with it, and every later read of `workerLoaded` would wait for a\n * failure or a teardown instead of resolving. The guarded dispatch serves every listener and\n * writes the retained value, because the event was delivered.\n *\n * What the listeners threw arrives here afterwards -- one of them unchanged, several as an\n * `AggregateError` in dispatch order -- and is reported. It goes no further: this runs from a\n * microtask, where a throw has no caller left to reach and would surface as an unhandled error.\n */\n private announceLoaded(): void {\n try {\n emitStrict(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerLoaded, this);\n } catch (error) {\n this.logger.error('a workerLoaded listener threw', error);\n }\n }\n\n /**\n * Announces the failure to the consumers. The same guarded dispatch as\n * {@link RemoteWorkerEnv.announceLoaded}, for the same reason: `WorkerFailed` is documented as\n * retained, and a consumer that subscribes only after the failure has to be able to hear about\n * it. Every listener is served, the retained value is written, and whatever was thrown is\n * reported here and reaches no caller.\n */\n private announceFailure(payload: WorkerFailedEvent): void {\n try {\n emitStrict(this as RemoteWorkerEnv, RemoteWorkerEnv.WorkerFailed, payload);\n } catch (error) {\n this.logger.error('a workerFailed listener threw', error);\n }\n }\n\n private readonly onMessageFromWorker = (event: MessageEvent): void => {\n if (event.data?.type === MessageToView) {\n (this as IShadowObjectEnvProxy).onMessageToView?.(event.data.data);\n } else {\n this.logger.debug('message from worker', event);\n }\n };\n\n /**\n * Takes every listener of this environment off a worker. Whoever registered them takes them\n * off again -- between the teardown and the `terminate()` that ends it the worker stays\n * alive, and through its listeners it keeps this environment and everything it references\n * reachable for exactly that long.\n */\n private stopListeningTo(worker: Worker): void {\n worker.removeEventListener('error', this.onWorkerError);\n worker.removeEventListener('messageerror', this.onWorkerMessageError);\n worker.removeEventListener('message', this.onMessageFromWorker);\n }\n\n private configureConsoleLogger(worker: Worker) {\n const configKey = ['RemoteWorkerEnv', 'workerConfig'];\n const storageKey = consoleLoggerConfigKey(configKey);\n const workerConfig = this.readWorkerConfig(configKey, storageKey);\n\n this.logger.info('load console-logger worker config', {storageKey, workerConfig});\n\n worker.postMessage({\n type: CONSOLE_LOGGER,\n config: {\n ...ConsoleLogger.sharedConfig,\n enable: this.logger.isEnabled,\n ...workerConfig,\n ...(ConsoleLogger.isEnabled ? {} : {enable: false}),\n },\n });\n }\n\n /**\n * The console-logger config for the worker, read from the storage key this environment\n * announces. The key is a diagnostic switch, set by hand and sharing its namespace with\n * everything else on this origin, so its content decides nothing about whether the worker\n * comes up: anything that is not a plain JSON object counts as no config at all, exactly\n * like an absent key.\n */\n private readWorkerConfig(configKey: string[], storageKey: string): Record<string, unknown> {\n const stored = loadConsoleLoggerConfig(configKey, '{}');\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stored);\n } catch (error) {\n // error, not warn: whoever writes this key wants the worker to talk, and a silent\n // fallback would send them looking for the reason inside the worker\n this.logger.error(`ignoring the unreadable console-logger worker config at \"${storageKey}\"`, stored, error);\n return {};\n }\n\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n this.logger.error(`ignoring the console-logger worker config at \"${storageKey}\": expected a JSON object`, stored);\n return {};\n }\n\n return parsed as Record<string, unknown>;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/src/view/ShadowEnv.d.ts
CHANGED
|
@@ -5,14 +5,30 @@ import type { IShadowObjectEnvProxy } from './IShadowObjectEnvProxy.js';
|
|
|
5
5
|
declare global {
|
|
6
6
|
var __shadowEnvs: Map<NamespaceType, ShadowEnv> | undefined;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The reason every pending {@link ShadowEnv.ready} and {@link ShadowEnv.syncWait} promise
|
|
10
|
+
* is rejected with when the environment is destroyed.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ShadowEnvDestroyedError extends Error {
|
|
13
|
+
constructor(message?: string);
|
|
14
|
+
}
|
|
8
15
|
export declare class ShadowEnv {
|
|
9
16
|
#private;
|
|
10
17
|
static AfterSync: string;
|
|
18
|
+
static SyncFailed: string;
|
|
11
19
|
static ContextLost: string;
|
|
12
20
|
static ContextCreated: string;
|
|
21
|
+
static ProxyFailed: string;
|
|
13
22
|
static get(ns: NamespaceType): ShadowEnv | undefined;
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
/** The logger this environment reports through. */
|
|
24
|
+
get logger(): ConsoleLogger;
|
|
25
|
+
/**
|
|
26
|
+
* The namespace of the {@link ComponentContext} this environment observes, and `undefined`
|
|
27
|
+
* while it observes none. The `view` setter writes it, so it carries the name
|
|
28
|
+
* {@link ShadowEnv.get} finds this environment under, unless another environment has since
|
|
29
|
+
* taken that namespace over; a `destroy()` leaves it on `undefined`.
|
|
30
|
+
*/
|
|
31
|
+
readonly ns$: import("@spearwolf/signalize").Signal<NamespaceType | undefined>;
|
|
16
32
|
accessor viewReady: boolean;
|
|
17
33
|
accessor proxyReady: boolean;
|
|
18
34
|
get isDestroyed(): boolean;
|
|
@@ -22,9 +38,64 @@ export declare class ShadowEnv {
|
|
|
22
38
|
get envProxy(): IShadowObjectEnvProxy | undefined;
|
|
23
39
|
set envProxy(proxy: IShadowObjectEnvProxy | null | undefined);
|
|
24
40
|
get isReady(): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Resolves once the environment is ready.
|
|
43
|
+
*
|
|
44
|
+
* @throws {ShadowEnvDestroyedError} if the environment is destroyed before that happens
|
|
45
|
+
*/
|
|
25
46
|
readonly ready: () => Promise<ShadowEnv>;
|
|
47
|
+
/**
|
|
48
|
+
* Send the change trail without asking the Shadow Environment to confirm it --
|
|
49
|
+
* {@link ShadowEnv.syncWait} is the call that asks.
|
|
50
|
+
*
|
|
51
|
+
* What becomes of a refusal is then the proxy's decision, and the two shipped ones differ.
|
|
52
|
+
* `LocalShadowObjectEnv` runs the Kernel synchronously, inside the call the environment makes to
|
|
53
|
+
* the proxy -- one microtask after `sync()` returned, not inside the call to `sync()` itself --
|
|
54
|
+
* and rejects with what it threw, so a refusal reaches {@link ShadowEnv.SyncFailed} here as well,
|
|
55
|
+
* carrying its number.
|
|
56
|
+
* `RemoteWorkerEnv` sends no serial without a confirmation and gets no answer back, so a
|
|
57
|
+
* refusal stays in the worker: it is written to the console there, `SyncFailed` stays silent, and
|
|
58
|
+
* the whole trail is booked as applied.
|
|
59
|
+
*
|
|
60
|
+
* {@link ShadowEnv.syncWait} is the way both proxies answer on.
|
|
61
|
+
*/
|
|
26
62
|
sync(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Like {@link ShadowEnv.sync}, but resolves with the change trail once the cycle completed.
|
|
65
|
+
*
|
|
66
|
+
* A cycle whose change trail the Shadow Environment could not apply rejects instead, with the
|
|
67
|
+
* reason the proxy gave. Where the Kernel itself refused the trail that reason is a
|
|
68
|
+
* {@link ChangeTrailRefusedError}: it names how many entries the Kernel applied, this side folds
|
|
69
|
+
* exactly those into its bookkeeping, and everything behind that line stays pending and goes out
|
|
70
|
+
* again with the next cycle. A trail whose cause of refusal stays put is refused every time, which
|
|
71
|
+
* is what {@link ShadowEnv.SyncFailed} is the place to act on.
|
|
72
|
+
*
|
|
73
|
+
* A reason that says nothing about how far the Kernel got -- a confirmation window that ran out,
|
|
74
|
+
* a proxy whose environment is gone -- counts the whole trail as applied, because a Shadow
|
|
75
|
+
* Environment that fell silent may well hold all of it. Then the trail is gone, and only a
|
|
76
|
+
* re-creation from the Component Memory brings it back. That re-creation belongs to a fresh
|
|
77
|
+
* proxy: an environment that still holds the entities refuses a creation for a uuid it already
|
|
78
|
+
* has. Handing {@link ShadowEnv.envProxy} a new proxy is therefore the way back -- the Shadow
|
|
79
|
+
* Environment calls {@link ComponentContext.reCreateChanges} itself once the new proxy is ready.
|
|
80
|
+
* Making that call is the consumer's decision, the same way recovering from a
|
|
81
|
+
* {@link ShadowEnv.ProxyFailed} is.
|
|
82
|
+
*
|
|
83
|
+
* Which cycle a caller gets is decided when the change trail is built. Everyone who arrives
|
|
84
|
+
* before that point waits on the same promise -- they all ride the same trail. From the build
|
|
85
|
+
* on the trail is fixed, and a call after it belongs to the next cycle, the one that carries
|
|
86
|
+
* the changes made since. That holds inside a listener of {@link ShadowEnv.AfterSync} or
|
|
87
|
+
* {@link ShadowEnv.SyncFailed} as well: the cycle it was told about is over, so the call opens
|
|
88
|
+
* the one behind it.
|
|
89
|
+
*
|
|
90
|
+
* @throws {ShadowEnvDestroyedError} if the environment is destroyed before the cycle completes
|
|
91
|
+
*/
|
|
27
92
|
syncWait(): Promise<ChangeTrailType>;
|
|
93
|
+
/**
|
|
94
|
+
* Tear the environment down: the proxy is destroyed, the namespace is released, all signals
|
|
95
|
+
* and listeners are removed, and every caller still waiting on {@link ShadowEnv.ready} or
|
|
96
|
+
* {@link ShadowEnv.syncWait} is rejected with a {@link ShadowEnvDestroyedError} instead of
|
|
97
|
+
* being left pending forever. Calling it more than once is a no-op.
|
|
98
|
+
*/
|
|
28
99
|
destroy(): void;
|
|
29
100
|
}
|
|
30
101
|
//# sourceMappingURL=ShadowEnv.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShadowEnv.d.ts","sourceRoot":"","sources":["../../../src/view/ShadowEnv.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ShadowEnv.d.ts","sourceRoot":"","sources":["../../../src/view/ShadowEnv.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,eAAe,EAAE,aAAa,EAAC,MAAM,aAAa,CAAC;AAChE,OAAO,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,4BAA4B,CAAC;AAEtE,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,YAAY,EAAE,GAAG,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC;CAC7D;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAO,SAA8C,EAGhE;CACF;AAcD,qBAAa,SAAS;;IACpB,MAAM,CAAC,SAAS,SAAe;IAC/B,MAAM,CAAC,UAAU,SAAgB;IACjC,MAAM,CAAC,WAAW,SAAiB;IACnC,MAAM,CAAC,cAAc,SAAoB;IACzC,MAAM,CAAC,WAAW,SAAiB;IAEnC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,aAAa,GAAG,SAAS,GAAG,SAAS,CAGnD;IAWD,mDAAmD;IACnD,IAAI,MAAM,IAAI,aAAa,CAE1B;IAED;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,mEAA6C;IAE/C,QAAQ,CAAC,SAAS,UAAS;IAC3B,QAAQ,CAAC,UAAU,UAAS;IAItC,IAAI,WAAW,YAEd;IAED,cAWC;IAoDD,IAAI,IAAI,IAAI,gBAAgB,GAAG,SAAS,CAEvC;IAED,IAAI,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EA0BhD;IAkBD,IAAI,QAAQ,IAAI,qBAAqB,GAAG,SAAS,CAEhD;IAMD,IAAI,QAAQ,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,EA+C3D;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAuBD;;;;OAIG;IACH,QAAQ,CAAC,KAAK,QAAa,OAAO,CAAC,SAAS,CAAC,CAI3C;IAEF;;;;;;;;;;;;;;OAcG;IACH,IAAI,IAAI,IAAI,CASX;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAanC;IAwBD;;;;;OAKG;IACH,OAAO,SAgCN;CA2JF"}
|