@spearwolf/shadow-objects 0.33.0 → 0.35.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 +350 -61
- package/README.md +66 -13
- package/bundle.js +6 -53
- package/package.json +15 -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 +26 -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 +13 -2
- package/src/constants.d.ts.map +1 -1
- package/src/constants.js +7 -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 +39 -1
- package/src/elements/ShaeEntElement.d.ts.map +1 -1
- package/src/elements/ShaeEntElement.js +397 -167
- 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 +61 -2
- package/src/elements/ShaePropElement.d.ts.map +1 -1
- package/src/elements/ShaePropElement.js +264 -219
- package/src/elements/ShaePropElement.js.map +2 -2
- package/src/elements/ShaeWorkerElement.d.ts +59 -5
- package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
- package/src/elements/ShaeWorkerElement.js +271 -73
- package/src/elements/ShaeWorkerElement.js.map +2 -2
- package/src/elements/constants.d.ts +9 -0
- package/src/elements/constants.d.ts.map +1 -1
- package/src/elements/constants.js +18 -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 +143 -3
- package/src/in-the-dark/Entity.d.ts.map +1 -1
- package/src/in-the-dark/Entity.js +330 -63
- package/src/in-the-dark/Entity.js.map +3 -3
- package/src/in-the-dark/Kernel.d.ts +98 -16
- package/src/in-the-dark/Kernel.d.ts.map +1 -1
- package/src/in-the-dark/Kernel.js +466 -379
- package/src/in-the-dark/Kernel.js.map +3 -3
- package/src/in-the-dark/Registry.d.ts +7 -1
- package/src/in-the-dark/Registry.d.ts.map +1 -1
- package/src/in-the-dark/Registry.js +112 -14
- package/src/in-the-dark/Registry.js.map +2 -2
- package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
- package/src/in-the-dark/ShadowObject.js +1 -0
- package/src/in-the-dark/ShadowObject.js.map +2 -2
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts +133 -0
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js +603 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
- package/src/in-the-dark/SignalsPath.d.ts +2 -0
- package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
- package/src/in-the-dark/SignalsPath.js +6 -2
- package/src/in-the-dark/SignalsPath.js.map +2 -2
- package/src/in-the-dark/displayName.d.ts +4 -0
- package/src/in-the-dark/displayName.d.ts.map +1 -0
- package/src/in-the-dark/displayName.js +5 -0
- package/src/in-the-dark/displayName.js.map +7 -0
- 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 +5 -2
- package/src/in-the-dark/importModule.js.map +2 -2
- package/src/index.d.ts +9 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +9 -1
- package/src/index.js.map +2 -2
- package/src/inspect/createKernelSnapshot.d.ts +17 -0
- package/src/inspect/createKernelSnapshot.d.ts.map +1 -0
- package/src/inspect/createKernelSnapshot.js +247 -0
- package/src/inspect/createKernelSnapshot.js.map +7 -0
- package/src/inspect/createViewSnapshot.d.ts +10 -0
- package/src/inspect/createViewSnapshot.d.ts.map +1 -0
- package/src/inspect/createViewSnapshot.js +143 -0
- package/src/inspect/createViewSnapshot.js.map +7 -0
- package/src/inspect/normalizeInspectRequest.d.ts +32 -0
- package/src/inspect/normalizeInspectRequest.d.ts.map +1 -0
- package/src/inspect/normalizeInspectRequest.js +55 -0
- package/src/inspect/normalizeInspectRequest.js.map +7 -0
- package/src/inspect/serializeValue.d.ts +13 -0
- package/src/inspect/serializeValue.d.ts.map +1 -0
- package/src/inspect/serializeValue.js +149 -0
- package/src/inspect/serializeValue.js.map +7 -0
- package/src/inspect/types.d.ts +260 -0
- package/src/inspect/types.d.ts.map +1 -0
- package/src/inspect/types.js +1 -0
- package/src/inspect/types.js.map +7 -0
- package/src/model-context/ModelContextLike.d.ts +59 -0
- package/src/model-context/ModelContextLike.d.ts.map +1 -0
- package/src/model-context/ModelContextLike.js +15 -0
- package/src/model-context/ModelContextLike.js.map +7 -0
- package/src/model-context/exposeShadowEnvsToModelContext.d.ts +48 -0
- package/src/model-context/exposeShadowEnvsToModelContext.d.ts.map +1 -0
- package/src/model-context/exposeShadowEnvsToModelContext.js +39 -0
- package/src/model-context/exposeShadowEnvsToModelContext.js.map +7 -0
- package/src/model-context/redactProps.d.ts +14 -0
- package/src/model-context/redactProps.d.ts.map +1 -0
- package/src/model-context/redactProps.js +22 -0
- package/src/model-context/redactProps.js.map +7 -0
- package/src/model-context/sharedExposure.d.ts +32 -0
- package/src/model-context/sharedExposure.d.ts.map +1 -0
- package/src/model-context/sharedExposure.js +108 -0
- package/src/model-context/sharedExposure.js.map +7 -0
- package/src/model-context/toolSupport.d.ts +72 -0
- package/src/model-context/toolSupport.d.ts.map +1 -0
- package/src/model-context/toolSupport.js +145 -0
- package/src/model-context/toolSupport.js.map +7 -0
- package/src/model-context/tools/findEntities.d.ts +15 -0
- package/src/model-context/tools/findEntities.d.ts.map +1 -0
- package/src/model-context/tools/findEntities.js +71 -0
- package/src/model-context/tools/findEntities.js.map +7 -0
- package/src/model-context/tools/getEntity.d.ts +16 -0
- package/src/model-context/tools/getEntity.d.ts.map +1 -0
- package/src/model-context/tools/getEntity.js +65 -0
- package/src/model-context/tools/getEntity.js.map +7 -0
- package/src/model-context/tools/getEntityTree.d.ts +4 -0
- package/src/model-context/tools/getEntityTree.d.ts.map +1 -0
- package/src/model-context/tools/getEntityTree.js +58 -0
- package/src/model-context/tools/getEntityTree.js.map +7 -0
- package/src/model-context/tools/getRegistry.d.ts +14 -0
- package/src/model-context/tools/getRegistry.d.ts.map +1 -0
- package/src/model-context/tools/getRegistry.js +39 -0
- package/src/model-context/tools/getRegistry.js.map +7 -0
- package/src/model-context/tools/index.d.ts +9 -0
- package/src/model-context/tools/index.d.ts.map +1 -0
- package/src/model-context/tools/index.js +16 -0
- package/src/model-context/tools/index.js.map +7 -0
- package/src/model-context/tools/listEnvs.d.ts +33 -0
- package/src/model-context/tools/listEnvs.d.ts.map +1 -0
- package/src/model-context/tools/listEnvs.js +37 -0
- package/src/model-context/tools/listEnvs.js.map +7 -0
- package/src/model-context.d.ts +13 -0
- package/src/model-context.d.ts.map +1 -0
- package/src/model-context.js +15 -0
- package/src/model-context.js.map +7 -0
- package/src/shadow-objects.d.ts +4 -0
- package/src/shadow-objects.d.ts.map +1 -1
- package/src/shadow-objects.js +6 -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/testing/TestEntity.d.ts +79 -0
- package/src/testing/TestEntity.d.ts.map +1 -0
- package/src/testing/TestEntity.js +135 -0
- package/src/testing/TestEntity.js.map +7 -0
- package/src/testing/createTestKernel.d.ts +11 -0
- package/src/testing/createTestKernel.d.ts.map +1 -0
- package/src/testing/createTestKernel.js +132 -0
- package/src/testing/createTestKernel.js.map +7 -0
- package/src/testing/mountShadowObject.d.ts +21 -0
- package/src/testing/mountShadowObject.d.ts.map +1 -0
- package/src/testing/mountShadowObject.js +72 -0
- package/src/testing/mountShadowObject.js.map +7 -0
- package/src/testing/recordKernelErrors.d.ts +21 -0
- package/src/testing/recordKernelErrors.d.ts.map +1 -0
- package/src/testing/recordKernelErrors.js +39 -0
- package/src/testing/recordKernelErrors.js.map +7 -0
- package/src/testing/settle.d.ts +15 -0
- package/src/testing/settle.d.ts.map +1 -0
- package/src/testing/settle.js +19 -0
- package/src/testing/settle.js.map +7 -0
- package/src/testing/types.d.ts +119 -0
- package/src/testing/types.d.ts.map +1 -0
- package/src/testing/types.js +1 -0
- package/src/testing/types.js.map +7 -0
- package/src/testing.d.ts +14 -0
- package/src/testing.d.ts.map +1 -0
- package/src/testing.js +11 -0
- package/src/testing.js.map +7 -0
- package/src/types.d.ts +185 -14
- 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 +51 -26
- 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 -10
- 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 +11 -0
- package/src/utils/attr-utils.d.ts.map +1 -1
- package/src/utils/attr-utils.js +5 -1
- package/src/utils/attr-utils.js.map +2 -2
- package/src/utils/constants.js.map +1 -1
- package/src/utils/generateUUID.d.ts +11 -0
- 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 +12 -1
- package/src/utils/waitForMessageOfType.d.ts.map +1 -1
- package/src/utils/waitForMessageOfType.js +16 -1
- package/src/utils/waitForMessageOfType.js.map +2 -2
- package/src/view/ComponentChanges.d.ts +88 -1
- package/src/view/ComponentChanges.d.ts.map +1 -1
- package/src/view/ComponentChanges.js +243 -73
- package/src/view/ComponentChanges.js.map +2 -2
- package/src/view/ComponentContext.d.ts +206 -16
- package/src/view/ComponentContext.d.ts.map +1 -1
- package/src/view/ComponentContext.js +548 -91
- 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.map +1 -1
- package/src/view/IShadowObjectEnvProxy.d.ts +38 -3
- package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.d.ts +8 -1
- package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.js +23 -7
- package/src/view/LocalShadowObjectEnv.js.map +2 -2
- package/src/view/RemoteWorkerEnv.d.ts +108 -5
- package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
- package/src/view/RemoteWorkerEnv.js +244 -77
- package/src/view/RemoteWorkerEnv.js.map +2 -2
- package/src/view/ShadowEnv.d.ts +79 -2
- package/src/view/ShadowEnv.d.ts.map +1 -1
- package/src/view/ShadowEnv.js +323 -59
- package/src/view/ShadowEnv.js.map +3 -3
- package/src/view/ViewComponent.d.ts +78 -14
- package/src/view/ViewComponent.d.ts.map +1 -1
- package/src/view/ViewComponent.js +90 -14
- 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 +98 -15
- 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,8 +1,27 @@
|
|
|
1
1
|
const CONSOLE_LOGGER = "ConsoleLogger";
|
|
2
2
|
const CONSOLE_LOGGER_STORAGE = `${CONSOLE_LOGGER}Storage`;
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
4
|
+
const IS_LOOPBACK_HOST = LOOPBACK_HOSTNAMES.has(globalThis.location?.hostname ?? "");
|
|
5
|
+
const gLocalStorage = (() => {
|
|
6
|
+
try {
|
|
7
|
+
const storage = globalThis.localStorage;
|
|
8
|
+
if (typeof storage?.getItem !== "function" || typeof storage.setItem !== "function" || typeof storage.removeItem !== "function") {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
const probeKey = `${CONSOLE_LOGGER}.probe`;
|
|
12
|
+
try {
|
|
13
|
+
storage.setItem(probeKey, probeKey);
|
|
14
|
+
} finally {
|
|
15
|
+
storage.removeItem(probeKey);
|
|
16
|
+
}
|
|
17
|
+
return storage;
|
|
18
|
+
} catch {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
const HAS_LOCAL_STORAGE = gLocalStorage != null;
|
|
5
23
|
const ConsoleLogger$ = Symbol.for(CONSOLE_LOGGER);
|
|
24
|
+
const gGlobalSlots = globalThis;
|
|
6
25
|
let gInitialized = false;
|
|
7
26
|
const asBoolean = (val) => {
|
|
8
27
|
if (typeof val === "boolean") return val;
|
|
@@ -18,21 +37,25 @@ const asBoolean = (val) => {
|
|
|
18
37
|
const getKeyPath = (key) => [HAS_LOCAL_STORAGE ? CONSOLE_LOGGER : void 0, ...Array.isArray(key) ? key : [key]].filter(Boolean).join(".");
|
|
19
38
|
function loadConfigValue(key, as, defaultValue) {
|
|
20
39
|
const _key = getKeyPath(key);
|
|
21
|
-
const value =
|
|
40
|
+
const value = gLocalStorage ? gLocalStorage.getItem(_key) : gGlobalSlots.ConsoleLoggerStorage?.[_key];
|
|
22
41
|
if (value == void 0) return defaultValue;
|
|
23
42
|
return as ? as(value) : value;
|
|
24
43
|
}
|
|
25
44
|
function saveConfigValue(key, val) {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
} else {
|
|
29
|
-
if (globalThis[CONSOLE_LOGGER_STORAGE] == void 0) {
|
|
30
|
-
globalThis[CONSOLE_LOGGER_STORAGE] = {};
|
|
31
|
-
console.debug(`${CONSOLE_LOGGER}: Initialize`, { [CONSOLE_LOGGER_STORAGE]: globalThis[CONSOLE_LOGGER_STORAGE] });
|
|
32
|
-
}
|
|
33
|
-
globalThis[CONSOLE_LOGGER_STORAGE][getKeyPath(key)] = val;
|
|
45
|
+
if (gLocalStorage) {
|
|
46
|
+
gLocalStorage.setItem(getKeyPath(key), val);
|
|
34
47
|
}
|
|
35
48
|
}
|
|
49
|
+
const consoleLoggerConfigKey = (key) => getKeyPath(key);
|
|
50
|
+
const loadConsoleLoggerConfig = (key, defaultValue) => loadConfigValue(key, void 0, defaultValue);
|
|
51
|
+
function setConsoleLoggerStorage(config) {
|
|
52
|
+
const store = gGlobalSlots.ConsoleLoggerStorage;
|
|
53
|
+
if (store != null && store === ConsoleLogger.sharedConfig) {
|
|
54
|
+
Object.assign(store, config, { [ConsoleLogger$]: true });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
gGlobalSlots.ConsoleLoggerStorage = config;
|
|
58
|
+
}
|
|
36
59
|
class ConsoleLogger {
|
|
37
60
|
constructor(namespace) {
|
|
38
61
|
this.enable = true;
|
|
@@ -43,11 +66,10 @@ class ConsoleLogger {
|
|
|
43
66
|
}
|
|
44
67
|
const key = [this.namespace, "enable"];
|
|
45
68
|
this.enable = loadConfigValue(key, asBoolean, this.enable);
|
|
46
|
-
saveConfigValue(key, HAS_LOCAL_STORAGE ? this.enable ? "true" : "false" : this.enable);
|
|
47
69
|
}
|
|
48
70
|
static {
|
|
49
71
|
this.sharedConfig = {
|
|
50
|
-
enable:
|
|
72
|
+
enable: IS_LOOPBACK_HOST,
|
|
51
73
|
debug: false,
|
|
52
74
|
info: true,
|
|
53
75
|
warn: true,
|
|
@@ -102,8 +124,8 @@ class ConsoleLogger {
|
|
|
102
124
|
if (ConsoleLogger.isDebug) {
|
|
103
125
|
console.debug(`${CONSOLE_LOGGER}: Load config from localStorage`, ConsoleLogger.sharedConfig);
|
|
104
126
|
}
|
|
105
|
-
if (!
|
|
106
|
-
|
|
127
|
+
if (!gGlobalSlots.ConsoleLogger?.[ConsoleLogger$]) {
|
|
128
|
+
gGlobalSlots.ConsoleLogger ??= {
|
|
107
129
|
[ConsoleLogger$]: true,
|
|
108
130
|
get enable() {
|
|
109
131
|
return ConsoleLogger.sharedConfig.enable;
|
|
@@ -136,20 +158,15 @@ class ConsoleLogger {
|
|
|
136
158
|
};
|
|
137
159
|
}
|
|
138
160
|
} else {
|
|
139
|
-
if (!
|
|
140
|
-
|
|
161
|
+
if (!gGlobalSlots.ConsoleLoggerStorage?.[ConsoleLogger$]) {
|
|
162
|
+
gGlobalSlots.ConsoleLoggerStorage = {
|
|
141
163
|
[ConsoleLogger$]: true,
|
|
142
164
|
...ConsoleLogger.sharedConfig,
|
|
143
|
-
|
|
144
|
-
...globalThis[CONSOLE_LOGGER_STORAGE]
|
|
165
|
+
...gGlobalSlots.ConsoleLoggerStorage
|
|
145
166
|
};
|
|
146
|
-
ConsoleLogger.sharedConfig =
|
|
167
|
+
ConsoleLogger.sharedConfig = gGlobalSlots.ConsoleLoggerStorage;
|
|
147
168
|
if (ConsoleLogger.isDebug) {
|
|
148
|
-
console.debug(
|
|
149
|
-
`${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`,
|
|
150
|
-
// @ts-ignore
|
|
151
|
-
globalThis[CONSOLE_LOGGER_STORAGE]
|
|
152
|
-
);
|
|
169
|
+
console.debug(`${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`, gGlobalSlots.ConsoleLoggerStorage);
|
|
153
170
|
}
|
|
154
171
|
}
|
|
155
172
|
}
|
|
@@ -167,14 +184,19 @@ class ConsoleLogger {
|
|
|
167
184
|
return this.isEnabled && ConsoleLogger.sharedConfig.warn;
|
|
168
185
|
}
|
|
169
186
|
debug(...args) {
|
|
187
|
+
if (!this.isDebug) return;
|
|
170
188
|
this.#print("debug", ConsoleLogger.sharedStyles.debug, args);
|
|
171
189
|
}
|
|
172
190
|
info(...args) {
|
|
191
|
+
if (!this.isInfo) return;
|
|
173
192
|
this.#print("info", ConsoleLogger.sharedStyles.info, args);
|
|
174
193
|
}
|
|
175
194
|
warn(...args) {
|
|
195
|
+
if (!this.isWarn) return;
|
|
176
196
|
this.#print("warn", ConsoleLogger.sharedStyles.warn, args);
|
|
177
197
|
}
|
|
198
|
+
// An error report names a fault in the calling code, and its author has to see it wherever
|
|
199
|
+
// the application runs -- not only on a loopback host, so `error()` asks no getter.
|
|
178
200
|
error(...args) {
|
|
179
201
|
this.#print("error", ConsoleLogger.sharedStyles.error, args);
|
|
180
202
|
}
|
|
@@ -185,6 +207,9 @@ class ConsoleLogger {
|
|
|
185
207
|
export {
|
|
186
208
|
CONSOLE_LOGGER,
|
|
187
209
|
CONSOLE_LOGGER_STORAGE,
|
|
188
|
-
ConsoleLogger
|
|
210
|
+
ConsoleLogger,
|
|
211
|
+
consoleLoggerConfigKey,
|
|
212
|
+
loadConsoleLoggerConfig,
|
|
213
|
+
setConsoleLoggerStorage
|
|
189
214
|
};
|
|
190
215
|
//# sourceMappingURL=ConsoleLogger.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/ConsoleLogger.ts"],
|
|
4
|
-
"sourcesContent": ["// https://developer.chrome.com/docs/devtools/console/format-style?hl=de\n\nexport const CONSOLE_LOGGER = 'ConsoleLogger';\nexport const CONSOLE_LOGGER_STORAGE = `${CONSOLE_LOGGER}Storage`;\n\nconst IS_LOCALHOST = Boolean(globalThis.location?.host?.startsWith('localhost') ?? false);\n\nconst HAS_LOCAL_STORAGE = 'localStorage' in globalThis;\n\nconst ConsoleLogger$ = Symbol.for(CONSOLE_LOGGER);\n\nlet gInitialized = false;\n\nconst asBoolean = (val: string | boolean) => {\n if (typeof val === 'boolean') return val;\n switch (val.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n return true;\n default:\n return false;\n }\n};\n\nconst getKeyPath = (key: string | string[]): string =>\n [HAS_LOCAL_STORAGE ? CONSOLE_LOGGER : undefined, ...(Array.isArray(key) ? key : [key])].filter(Boolean).join('.');\n\nfunction loadConfigValue<T>(key: string | string[], as: ((val: string) => T) | undefined, defaultValue: T): T {\n const _key = getKeyPath(key);\n // @ts-ignore\n const value = HAS_LOCAL_STORAGE ? localStorage.getItem(_key) : globalThis[CONSOLE_LOGGER_STORAGE]?.[_key];\n if (value == undefined) return defaultValue;\n // without a converter the stored value is the value: that is how the styles are read\n return as ? as(value) : (value as T);\n}\n\nfunction saveConfigValue(key: string | string[], val: any) {\n if (HAS_LOCAL_STORAGE) {\n localStorage.setItem(getKeyPath(key), val);\n } else {\n // @ts-ignore\n if (globalThis[CONSOLE_LOGGER_STORAGE] == undefined) {\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE] = {};\n // @ts-ignore\n console.debug(`${CONSOLE_LOGGER}: Initialize`, {[CONSOLE_LOGGER_STORAGE]: globalThis[CONSOLE_LOGGER_STORAGE]});\n }\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE][getKeyPath(key)] = val;\n }\n}\n\nexport class ConsoleLogger {\n namespace?: string;\n\n enable = true;\n\n static sharedConfig = {\n enable: IS_LOCALHOST,\n\n debug: false,\n info: true,\n warn: true,\n\n 'styles.debug': 'color: #111; background: #999; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.info': 'color: #020; background: #8a8; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.warn': 'color: #fa0; background: #a98; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.error': 'color: #ff0; background: #a00; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n };\n\n static get isEnabled() {\n return ConsoleLogger.sharedConfig.enable;\n }\n\n static get isDebug() {\n return ConsoleLogger.sharedConfig.enable && ConsoleLogger.sharedConfig.debug;\n }\n\n static sharedStyles = {\n get debug() {\n return ConsoleLogger.sharedConfig['styles.debug'];\n },\n set debug(val: string) {\n ConsoleLogger.sharedConfig['styles.debug'] = val;\n },\n get info() {\n return ConsoleLogger.sharedConfig['styles.info'];\n },\n set info(val: string) {\n ConsoleLogger.sharedConfig['styles.info'] = val;\n },\n get warn() {\n return ConsoleLogger.sharedConfig['styles.warn'];\n },\n set warn(val: string) {\n ConsoleLogger.sharedConfig['styles.warn'] = val;\n },\n get error() {\n return ConsoleLogger.sharedConfig['styles.error'];\n },\n set error(val: string) {\n ConsoleLogger.sharedConfig['styles.error'] = val;\n },\n };\n\n static loadConfig() {\n if (HAS_LOCAL_STORAGE) {\n ['enable', 'debug', 'info', 'warn'].forEach((key) => {\n // @ts-ignore\n this.sharedConfig[key] = loadConfigValue(key, asBoolean, this.sharedConfig[key]);\n });\n\n ['debug', 'info', 'warn', 'error'].forEach((key) => {\n // @ts-ignore\n this.sharedStyles[key] = loadConfigValue(['styles', key], undefined, this.sharedStyles[key]);\n });\n\n if (ConsoleLogger.isDebug) {\n console.debug(`${CONSOLE_LOGGER}: Load config from localStorage`, ConsoleLogger.sharedConfig);\n }\n\n // @ts-ignore\n if (!globalThis[CONSOLE_LOGGER]?.[ConsoleLogger$]) {\n // @ts-ignore\n globalThis[CONSOLE_LOGGER] ??= {\n [ConsoleLogger$]: true,\n\n get enable() {\n return ConsoleLogger.sharedConfig.enable;\n },\n set enable(val: boolean) {\n ConsoleLogger.sharedConfig.enable = val;\n saveConfigValue('enable', val ? 'true' : 'false');\n },\n get debug() {\n return ConsoleLogger.sharedConfig.debug;\n },\n set debug(val: boolean) {\n ConsoleLogger.sharedConfig.debug = val;\n saveConfigValue('debug', val ? 'true' : 'false');\n },\n get info() {\n return ConsoleLogger.sharedConfig.info;\n },\n set info(val: boolean) {\n ConsoleLogger.sharedConfig.info = val;\n saveConfigValue('info', val ? 'true' : 'false');\n },\n get warn() {\n return ConsoleLogger.sharedConfig.warn;\n },\n set warn(val: boolean) {\n ConsoleLogger.sharedConfig.warn = val;\n saveConfigValue('warn', val ? 'true' : 'false');\n },\n };\n }\n } else {\n // no localStorage available: in this case we just use globalThis[STORAGE_KEY]\n // @ts-ignore\n if (!globalThis[CONSOLE_LOGGER_STORAGE]?.[ConsoleLogger$]) {\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE] = {\n [ConsoleLogger$]: true,\n ...ConsoleLogger.sharedConfig,\n // @ts-ignore\n ...globalThis[CONSOLE_LOGGER_STORAGE],\n };\n // @ts-ignore\n ConsoleLogger.sharedConfig = globalThis[CONSOLE_LOGGER_STORAGE];\n if (ConsoleLogger.isDebug) {\n console.debug(\n `${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`,\n // @ts-ignore\n globalThis[CONSOLE_LOGGER_STORAGE],\n );\n }\n }\n }\n }\n\n constructor(namespace: string) {\n this.namespace = (namespace || '').trim() || CONSOLE_LOGGER;\n\n if (!gInitialized) {\n ConsoleLogger.loadConfig();\n gInitialized = true;\n }\n\n const key = [this.namespace, 'enable'];\n this.enable = loadConfigValue(key, asBoolean, this.enable);\n saveConfigValue(key, HAS_LOCAL_STORAGE ? (this.enable ? 'true' : 'false') : this.enable);\n }\n\n get isEnabled() {\n return this.enable && ConsoleLogger.sharedConfig.enable;\n }\n\n get isDebug() {\n return this.isEnabled && ConsoleLogger.sharedConfig.debug;\n }\n\n get isInfo() {\n return this.isEnabled && ConsoleLogger.sharedConfig.info;\n }\n\n get isWarn() {\n return this.isEnabled && ConsoleLogger.sharedConfig.warn;\n }\n\n debug(...args: any[]) {\n this.#print('debug', ConsoleLogger.sharedStyles.debug, args);\n }\n\n info(...args: any[]) {\n this.#print('info', ConsoleLogger.sharedStyles.info, args);\n }\n\n warn(...args: any[]) {\n this.#print('warn', ConsoleLogger.sharedStyles.warn, args);\n }\n\n error(...args: any[]) {\n this.#print('error', ConsoleLogger.sharedStyles.error, args);\n }\n\n #print(level: 'debug' | 'info' | 'warn' | 'error', styles: string, args: any[]) {\n console[level](`%c${this.namespace}`, styles, ...args);\n }\n}"],
|
|
5
|
-
"mappings": "AAEO,MAAM,iBAAiB;AACvB,MAAM,yBAAyB,GAAG,cAAc;
|
|
4
|
+
"sourcesContent": ["// https://developer.chrome.com/docs/devtools/console/format-style?hl=de\n\nexport const CONSOLE_LOGGER = 'ConsoleLogger';\nexport const CONSOLE_LOGGER_STORAGE = `${CONSOLE_LOGGER}Storage`;\n\n// A loopback host is the one place this library talks by default. `host` carries the port,\n// which a prefix test would have to get past -- and a prefix test says yes to every name that\n// merely begins with the word, `localhost.example.com` among them, while saying no to the\n// addresses that carry no name at all. So the port-free `hostname` is compared against the\n// exact set. A browser hands an IPv6 host over in brackets; the bare form is there for a\n// realm that does not.\nconst LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);\n\nconst IS_LOOPBACK_HOST = LOOPBACK_HOSTNAMES.has(globalThis.location?.hostname ?? '');\n\n// The name `localStorage` says nothing about a usable Storage behind it: node defines an inert\n// object on `globalThis`, and a browser with disabled cookies throws a `SecurityError` -- on the\n// property access or on the first write. So the capability is probed, not looked up, and the\n// storage that passes is kept by reference. Whatever fails here falls back to the plain object\n// under `globalThis.ConsoleLoggerStorage`.\nconst gLocalStorage: Storage | undefined = (() => {\n try {\n const storage = (globalThis as {localStorage?: Storage}).localStorage;\n if (\n typeof storage?.getItem !== 'function' ||\n typeof storage.setItem !== 'function' ||\n typeof storage.removeItem !== 'function'\n ) {\n return undefined;\n }\n // The write is what separates a Storage that only looks usable from one that is: cookies\n // may be off, or the quota full. This probe key is the only thing the library writes into a\n // host's storage on its own, and it is removed in the same breath -- everything past this\n // point only reads, until a caller reaches through the handle under `globalThis.ConsoleLogger`.\n // Two costs are accepted for the probe itself: one `storage` event pair for the other tabs of\n // this origin, and a full quota that sends an otherwise readable Storage into the fallback.\n const probeKey = `${CONSOLE_LOGGER}.probe`;\n try {\n storage.setItem(probeKey, probeKey);\n } finally {\n // reached even when `setItem` throws: a storage that takes the write and then fails would\n // otherwise fall through to the fallback with the probe key left behind. One way past\n // \"removed in the same breath\" stays open -- a `removeItem` that throws is the very call\n // this `finally` makes, and the key survives.\n storage.removeItem(probeKey);\n }\n return storage;\n } catch {\n return undefined;\n }\n})();\n\nconst HAS_LOCAL_STORAGE = gLocalStorage != null;\n\nconst ConsoleLogger$: unique symbol = Symbol.for(CONSOLE_LOGGER);\n\nexport interface ConsoleLoggerConfig {\n [ConsoleLogger$]?: boolean;\n enable: boolean;\n debug: boolean;\n info: boolean;\n warn: boolean;\n 'styles.debug': string;\n 'styles.info': string;\n 'styles.warn': string;\n 'styles.error': string;\n [key: string]: string | boolean | undefined;\n}\n\nexport interface ConsoleLoggerControl {\n [ConsoleLogger$]?: boolean;\n enable: boolean;\n debug: boolean;\n info: boolean;\n warn: boolean;\n}\n\n// A module-local view of the two globals this library touches, in place of a `declare global`:\n// the re-exports in this package's emitted `dist/src/index.d.ts`, published as `src/index.d.ts`,\n// reach every consumer's global type space through `ShadowEnv.d.ts`'s import of this module, and\n// `ConsoleLogger` is too common a name to claim there uninvited -- an ambient `var ConsoleLogger`\n// would collide with a consumer's own global of that name (`TS2403`) and turn a forgotten import\n// of this class into a silent pass against the handle instead of a compile error.\nconst gGlobalSlots = globalThis as typeof globalThis & {\n ConsoleLogger?: ConsoleLoggerControl;\n ConsoleLoggerStorage?: ConsoleLoggerConfig;\n};\n\nlet gInitialized = false;\n\nconst asBoolean = (val: string | boolean) => {\n if (typeof val === 'boolean') return val;\n switch (val.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n return true;\n default:\n return false;\n }\n};\n\nconst getKeyPath = (key: string | string[]): string =>\n [HAS_LOCAL_STORAGE ? CONSOLE_LOGGER : undefined, ...(Array.isArray(key) ? key : [key])].filter(Boolean).join('.');\n\nfunction loadConfigValue<T>(key: string | string[], as: ((val: string | boolean) => T) | undefined, defaultValue: T): T {\n const _key = getKeyPath(key);\n const value = gLocalStorage ? gLocalStorage.getItem(_key) : gGlobalSlots.ConsoleLoggerStorage?.[_key];\n if (value == undefined) return defaultValue;\n // without a converter the stored value is the value: that is how the styles are read\n return as ? as(value) : (value as T);\n}\n\n// The four setters of the `globalThis.ConsoleLogger` handle below are the only callers, and those\n// exist only inside the `HAS_LOCAL_STORAGE` branch of `loadConfig()` -- so `gLocalStorage` is\n// always set whenever this runs. The fallback store under `globalThis.ConsoleLoggerStorage` is\n// written by `loadConfig()` alone, and in the worker through `setConsoleLoggerStorage()`.\nfunction saveConfigValue(key: string | string[], val: any) {\n if (gLocalStorage) {\n gLocalStorage.setItem(getKeyPath(key), val);\n }\n}\n\n/**\n * The key a config value is stored under. Prefixed with {@link CONSOLE_LOGGER} when it goes\n * into a real Storage, where it shares the namespace with everything else on this origin.\n */\nexport const consoleLoggerConfigKey = (key: string | string[]): string => getKeyPath(key);\n\n/**\n * Reads a raw config value through whatever storage the capability probe accepted, or from the\n * fallback object store when none did. The way to reach the config from outside this module:\n * `globalThis.localStorage` may be missing, inert or hostile, and asking it directly throws.\n */\nexport const loadConsoleLoggerConfig = (key: string | string[], defaultValue: string): string =>\n loadConfigValue(key, undefined, defaultValue);\n\n/**\n * Installs a config object as the fallback store, bypassing the storage probe. `WorkerRuntime`\n * calls this with the config a `RemoteWorkerEnv` forwards from the main thread, where there is no\n * `localStorage` to probe in the first place.\n *\n * Once a logger exists in such a thread, `ConsoleLogger.sharedConfig` *is* that store, and the\n * values are written into it rather than a fresh object taking its place in the slot: the shared\n * switches reach every logger of the thread, the ones already built included. One flag stays\n * behind -- a logger reads its own `<namespace>.enable` key when it is built, and a config that\n * arrives afterwards no longer moves it.\n */\nexport function setConsoleLoggerStorage(config: ConsoleLoggerConfig): void {\n const store = gGlobalSlots.ConsoleLoggerStorage;\n if (store != null && store === ConsoleLogger.sharedConfig) {\n // the marker travels with the merge: it says this object is the live config, and a config\n // that crossed a worker boundary carries no symbol key to say so for itself\n Object.assign(store, config, {[ConsoleLogger$]: true});\n return;\n }\n gGlobalSlots.ConsoleLoggerStorage = config;\n}\n\nexport class ConsoleLogger {\n namespace?: string;\n\n enable = true;\n\n static sharedConfig: ConsoleLoggerConfig = {\n enable: IS_LOOPBACK_HOST,\n\n debug: false,\n info: true,\n warn: true,\n\n 'styles.debug': 'color: #111; background: #999; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.info': 'color: #020; background: #8a8; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.warn': 'color: #fa0; background: #a98; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n 'styles.error': 'color: #ff0; background: #a00; display: inline-block; padding: 0 0.25em; margin: 0; border-radius: 0.25em',\n };\n\n static get isEnabled() {\n return ConsoleLogger.sharedConfig.enable;\n }\n\n static get isDebug() {\n return ConsoleLogger.sharedConfig.enable && ConsoleLogger.sharedConfig.debug;\n }\n\n static sharedStyles = {\n get debug() {\n return ConsoleLogger.sharedConfig['styles.debug'];\n },\n set debug(val: string) {\n ConsoleLogger.sharedConfig['styles.debug'] = val;\n },\n get info() {\n return ConsoleLogger.sharedConfig['styles.info'];\n },\n set info(val: string) {\n ConsoleLogger.sharedConfig['styles.info'] = val;\n },\n get warn() {\n return ConsoleLogger.sharedConfig['styles.warn'];\n },\n set warn(val: string) {\n ConsoleLogger.sharedConfig['styles.warn'] = val;\n },\n get error() {\n return ConsoleLogger.sharedConfig['styles.error'];\n },\n set error(val: string) {\n ConsoleLogger.sharedConfig['styles.error'] = val;\n },\n };\n\n static loadConfig() {\n if (HAS_LOCAL_STORAGE) {\n (['enable', 'debug', 'info', 'warn'] as const).forEach((key) => {\n this.sharedConfig[key] = loadConfigValue(key, asBoolean, this.sharedConfig[key]);\n });\n\n (['debug', 'info', 'warn', 'error'] as const).forEach((key) => {\n this.sharedStyles[key] = loadConfigValue(['styles', key], undefined, this.sharedStyles[key]);\n });\n\n if (ConsoleLogger.isDebug) {\n console.debug(`${CONSOLE_LOGGER}: Load config from localStorage`, ConsoleLogger.sharedConfig);\n }\n\n if (!gGlobalSlots.ConsoleLogger?.[ConsoleLogger$]) {\n gGlobalSlots.ConsoleLogger ??= {\n [ConsoleLogger$]: true,\n\n get enable() {\n return ConsoleLogger.sharedConfig.enable;\n },\n set enable(val: boolean) {\n ConsoleLogger.sharedConfig.enable = val;\n saveConfigValue('enable', val ? 'true' : 'false');\n },\n get debug() {\n return ConsoleLogger.sharedConfig.debug;\n },\n set debug(val: boolean) {\n ConsoleLogger.sharedConfig.debug = val;\n saveConfigValue('debug', val ? 'true' : 'false');\n },\n get info() {\n return ConsoleLogger.sharedConfig.info;\n },\n set info(val: boolean) {\n ConsoleLogger.sharedConfig.info = val;\n saveConfigValue('info', val ? 'true' : 'false');\n },\n get warn() {\n return ConsoleLogger.sharedConfig.warn;\n },\n set warn(val: boolean) {\n ConsoleLogger.sharedConfig.warn = val;\n saveConfigValue('warn', val ? 'true' : 'false');\n },\n };\n }\n } else {\n // no localStorage available: in this case we just use gGlobalSlots.ConsoleLoggerStorage\n if (!gGlobalSlots.ConsoleLoggerStorage?.[ConsoleLogger$]) {\n gGlobalSlots.ConsoleLoggerStorage = {\n [ConsoleLogger$]: true,\n ...ConsoleLogger.sharedConfig,\n ...gGlobalSlots.ConsoleLoggerStorage,\n };\n ConsoleLogger.sharedConfig = gGlobalSlots.ConsoleLoggerStorage;\n if (ConsoleLogger.isDebug) {\n console.debug(`${CONSOLE_LOGGER}: Load config from ${CONSOLE_LOGGER_STORAGE}`, gGlobalSlots.ConsoleLoggerStorage);\n }\n }\n }\n }\n\n constructor(namespace: string) {\n this.namespace = (namespace || '').trim() || CONSOLE_LOGGER;\n\n if (!gInitialized) {\n ConsoleLogger.loadConfig();\n gInitialized = true;\n }\n\n // reads only: the storage of the host is never written here. The only way this library\n // writes to it is through the four setters of the `globalThis.ConsoleLogger` handle above.\n const key = [this.namespace, 'enable'];\n this.enable = loadConfigValue(key, asBoolean, this.enable);\n }\n\n get isEnabled() {\n return this.enable && ConsoleLogger.sharedConfig.enable;\n }\n\n get isDebug() {\n return this.isEnabled && ConsoleLogger.sharedConfig.debug;\n }\n\n get isInfo() {\n return this.isEnabled && ConsoleLogger.sharedConfig.info;\n }\n\n get isWarn() {\n return this.isEnabled && ConsoleLogger.sharedConfig.warn;\n }\n\n debug(...args: any[]) {\n if (!this.isDebug) return;\n this.#print('debug', ConsoleLogger.sharedStyles.debug, args);\n }\n\n info(...args: any[]) {\n if (!this.isInfo) return;\n this.#print('info', ConsoleLogger.sharedStyles.info, args);\n }\n\n warn(...args: any[]) {\n if (!this.isWarn) return;\n this.#print('warn', ConsoleLogger.sharedStyles.warn, args);\n }\n\n // An error report names a fault in the calling code, and its author has to see it wherever\n // the application runs -- not only on a loopback host, so `error()` asks no getter.\n error(...args: any[]) {\n this.#print('error', ConsoleLogger.sharedStyles.error, args);\n }\n\n #print(level: 'debug' | 'info' | 'warn' | 'error', styles: string, args: any[]) {\n console[level](`%c${this.namespace}`, styles, ...args);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/src/utils/FrameLoop.d.ts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
+
import { type ListenerFuncType } from '@spearwolf/eventize';
|
|
2
|
+
/**
|
|
3
|
+
* The payload of every {@link FrameLoop.OnFrame} event. All times are in seconds.
|
|
4
|
+
*/
|
|
5
|
+
export interface FrameData {
|
|
6
|
+
/** the timestamp of this frame */
|
|
7
|
+
now: number;
|
|
8
|
+
/** the timestamp of the frame before this one — equal to {@link FrameData.now} on the first frame */
|
|
9
|
+
lastNow: number;
|
|
10
|
+
/** the number of this frame, counted from one at the start of every run */
|
|
11
|
+
frameNo: number;
|
|
12
|
+
/** the time elapsed since the frame before this one — zero on the first frame */
|
|
13
|
+
deltaTime: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A loop that emits a frame to all of its targets as long as at least one of them listens.
|
|
17
|
+
*
|
|
18
|
+
* While no target listens, the loop asks for no frames at all.
|
|
19
|
+
*
|
|
20
|
+
* A target that throws costs itself its frame and nothing else: the frame still reaches every
|
|
21
|
+
* other target, and the loop asks for the frame behind it. The failure is reported through
|
|
22
|
+
* `console.warn` and reaches no caller -- see `#onFrame()` for why this loop answers a failing
|
|
23
|
+
* target that way and not through the `ConsoleLogger` the rest of this package reports through.
|
|
24
|
+
*/
|
|
1
25
|
export declare class FrameLoop {
|
|
2
26
|
#private;
|
|
3
27
|
static OnFrame: symbol;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The loop that every caller shares. It is created on first read and answers the same instance
|
|
30
|
+
* afterwards. It has no cap on the frame rate.
|
|
31
|
+
*/
|
|
32
|
+
static get(): FrameLoop;
|
|
33
|
+
constructor(maxFps?: number);
|
|
34
|
+
/** The upper bound on the frame rate. Zero means: no bound. */
|
|
35
|
+
get maxFps(): number;
|
|
36
|
+
set maxFps(fps: number);
|
|
37
|
+
/**
|
|
38
|
+
* How many targets listen for frames.
|
|
39
|
+
*
|
|
40
|
+
* OnFrame is the only event this object knows, so the total number of subscriptions is the
|
|
41
|
+
* number of frame targets.
|
|
42
|
+
*/
|
|
43
|
+
get subscriptionCount(): number;
|
|
44
|
+
start(target: object | ListenerFuncType): (() => void) | undefined;
|
|
45
|
+
stop(target: object | ListenerFuncType): void;
|
|
7
46
|
}
|
|
8
47
|
//# sourceMappingURL=FrameLoop.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FrameLoop.d.ts","sourceRoot":"","sources":["../../../src/utils/FrameLoop.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FrameLoop.d.ts","sourceRoot":"","sources":["../../../src/utils/FrameLoop.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,KAAK,gBAAgB,EAAU,MAAM,qBAAqB,CAAC;AAI7G;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,qGAAqG;IACrG,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,qBAAa,SAAS;;IACpB,MAAM,CAAC,OAAO,SAAqB;IAEnC;;;OAGG;IACH,MAAM,CAAC,GAAG,IAAI,SAAS,CAGtB;IAsBD,YAAY,MAAM,SAAI,EAGrB;IAED,+DAA+D;IAC/D,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,EAErB;IAED;;;;;OAKG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,4BAUtC;IAED,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,QAYrC;CA8DF"}
|
package/src/utils/FrameLoop.js
CHANGED
|
@@ -1,39 +1,100 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emitSafe, eventize, getSubscriptionCount, off, on } from "@spearwolf/eventize";
|
|
2
2
|
let gUniqInstance = null;
|
|
3
3
|
class FrameLoop {
|
|
4
4
|
static {
|
|
5
5
|
this.OnFrame = /* @__PURE__ */ Symbol("onFrame");
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* The loop that every caller shares. It is created on first read and answers the same instance
|
|
9
|
+
* afterwards. It has no cap on the frame rate.
|
|
10
|
+
*/
|
|
11
|
+
static get() {
|
|
12
|
+
gUniqInstance ??= new FrameLoop();
|
|
13
|
+
return gUniqInstance;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The share of the nominal frame time after which the next frame is due.
|
|
17
|
+
*
|
|
18
|
+
* Animation frames do not arrive at the nominal distance, and the browsers differ in how coarsely
|
|
19
|
+
* they round the timestamps they hand out. With `D` for the distance between real frames and `j`
|
|
20
|
+
* for the jitter on top of it, the factor has to sit between two bounds:
|
|
21
|
+
*
|
|
22
|
+
* - a cap at or above the refresh rate must lose no frame: `factor <= 1 - j / D`
|
|
23
|
+
* - a cap at half the refresh rate must drop every second frame: `factor > 0.5 + j / (2 * D)`
|
|
24
|
+
*
|
|
25
|
+
* At 60 Hz with the coarsest rounding measured — around 0.6 ms of jitter on a 16.67 ms
|
|
26
|
+
* distance — that leaves the range 0.52 to 0.96, and this value keeps its distance to both ends.
|
|
27
|
+
*/
|
|
28
|
+
static #DueFactor = 0.75;
|
|
7
29
|
#rafID = 0;
|
|
8
|
-
|
|
9
|
-
|
|
30
|
+
#maxFps = 0;
|
|
31
|
+
#frameNo = 0;
|
|
32
|
+
#lastNow = void 0;
|
|
33
|
+
constructor(maxFps = 0) {
|
|
10
34
|
eventize(this);
|
|
11
|
-
|
|
35
|
+
this.maxFps = maxFps;
|
|
36
|
+
}
|
|
37
|
+
/** The upper bound on the frame rate. Zero means: no bound. */
|
|
38
|
+
get maxFps() {
|
|
39
|
+
return this.#maxFps;
|
|
40
|
+
}
|
|
41
|
+
set maxFps(fps) {
|
|
42
|
+
this.#maxFps = Number.isFinite(fps) && fps > 0 ? fps : 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* How many targets listen for frames.
|
|
46
|
+
*
|
|
47
|
+
* OnFrame is the only event this object knows, so the total number of subscriptions is the
|
|
48
|
+
* number of frame targets.
|
|
49
|
+
*/
|
|
50
|
+
get subscriptionCount() {
|
|
51
|
+
return getSubscriptionCount(this);
|
|
12
52
|
}
|
|
13
53
|
start(target) {
|
|
14
54
|
if (target == null) return;
|
|
15
|
-
if (getSubscriptionCount(this) === 0) {
|
|
16
|
-
this.#requestAnimationFrame();
|
|
17
|
-
}
|
|
18
55
|
on(this, FrameLoop.OnFrame, target);
|
|
56
|
+
this.#requestAnimationFrame();
|
|
19
57
|
return () => {
|
|
20
58
|
this.stop(target);
|
|
21
59
|
};
|
|
22
60
|
}
|
|
23
61
|
stop(target) {
|
|
62
|
+
if (target == null) return;
|
|
24
63
|
off(this, FrameLoop.OnFrame, target);
|
|
25
|
-
if (
|
|
64
|
+
if (this.subscriptionCount === 0) {
|
|
26
65
|
this.#cancelAnimationFrame();
|
|
66
|
+
this.#lastNow = void 0;
|
|
67
|
+
this.#frameNo = 0;
|
|
27
68
|
}
|
|
28
69
|
}
|
|
29
70
|
#onFrame = (now) => {
|
|
30
|
-
|
|
31
|
-
this.#
|
|
71
|
+
this.#rafID = 0;
|
|
72
|
+
if (this.#isDue(now)) {
|
|
73
|
+
const lastNow = this.#lastNow ?? now;
|
|
74
|
+
this.#lastNow = now;
|
|
75
|
+
this.#frameNo += 1;
|
|
76
|
+
const frameData = {
|
|
77
|
+
now: now / 1e3,
|
|
78
|
+
lastNow: lastNow / 1e3,
|
|
79
|
+
frameNo: this.#frameNo,
|
|
80
|
+
deltaTime: (now - lastNow) / 1e3
|
|
81
|
+
};
|
|
82
|
+
emitSafe(this, FrameLoop.OnFrame, frameData);
|
|
83
|
+
}
|
|
84
|
+
if (this.subscriptionCount > 0) {
|
|
85
|
+
this.#requestAnimationFrame();
|
|
86
|
+
}
|
|
32
87
|
};
|
|
88
|
+
#isDue(now) {
|
|
89
|
+
if (this.#maxFps === 0 || this.#lastNow === void 0) return true;
|
|
90
|
+
return now - this.#lastNow >= FrameLoop.#DueFactor * (1e3 / this.#maxFps);
|
|
91
|
+
}
|
|
33
92
|
#requestAnimationFrame() {
|
|
93
|
+
if (this.#rafID !== 0) return;
|
|
34
94
|
this.#rafID = requestAnimationFrame(this.#onFrame);
|
|
35
95
|
}
|
|
36
96
|
#cancelAnimationFrame() {
|
|
97
|
+
if (this.#rafID === 0) return;
|
|
37
98
|
cancelAnimationFrame(this.#rafID);
|
|
38
99
|
this.#rafID = 0;
|
|
39
100
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/FrameLoop.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,SAAQ,
|
|
4
|
+
"sourcesContent": ["import {emitSafe, eventize, getSubscriptionCount, type ListenerFuncType, off, on} from '@spearwolf/eventize';\n\nlet gUniqInstance: FrameLoop | null = null;\n\n/**\n * The payload of every {@link FrameLoop.OnFrame} event. All times are in seconds.\n */\nexport interface FrameData {\n /** the timestamp of this frame */\n now: number;\n /** the timestamp of the frame before this one \u2014 equal to {@link FrameData.now} on the first frame */\n lastNow: number;\n /** the number of this frame, counted from one at the start of every run */\n frameNo: number;\n /** the time elapsed since the frame before this one \u2014 zero on the first frame */\n deltaTime: number;\n}\n\n/**\n * A loop that emits a frame to all of its targets as long as at least one of them listens.\n *\n * While no target listens, the loop asks for no frames at all.\n *\n * A target that throws costs itself its frame and nothing else: the frame still reaches every\n * other target, and the loop asks for the frame behind it. The failure is reported through\n * `console.warn` and reaches no caller -- see `#onFrame()` for why this loop answers a failing\n * target that way and not through the `ConsoleLogger` the rest of this package reports through.\n */\nexport class FrameLoop {\n static OnFrame = Symbol('onFrame');\n\n /**\n * The loop that every caller shares. It is created on first read and answers the same instance\n * afterwards. It has no cap on the frame rate.\n */\n static get(): FrameLoop {\n gUniqInstance ??= new FrameLoop();\n return gUniqInstance;\n }\n\n /**\n * The share of the nominal frame time after which the next frame is due.\n *\n * Animation frames do not arrive at the nominal distance, and the browsers differ in how coarsely\n * they round the timestamps they hand out. With `D` for the distance between real frames and `j`\n * for the jitter on top of it, the factor has to sit between two bounds:\n *\n * - a cap at or above the refresh rate must lose no frame: `factor <= 1 - j / D`\n * - a cap at half the refresh rate must drop every second frame: `factor > 0.5 + j / (2 * D)`\n *\n * At 60 Hz with the coarsest rounding measured \u2014 around 0.6 ms of jitter on a 16.67 ms\n * distance \u2014 that leaves the range 0.52 to 0.96, and this value keeps its distance to both ends.\n */\n static #DueFactor = 0.75;\n\n #rafID = 0;\n #maxFps = 0;\n #frameNo = 0;\n #lastNow: number | undefined = undefined;\n\n constructor(maxFps = 0) {\n eventize(this);\n this.maxFps = maxFps;\n }\n\n /** The upper bound on the frame rate. Zero means: no bound. */\n get maxFps(): number {\n return this.#maxFps;\n }\n\n set maxFps(fps: number) {\n this.#maxFps = Number.isFinite(fps) && fps > 0 ? fps : 0;\n }\n\n /**\n * How many targets listen for frames.\n *\n * OnFrame is the only event this object knows, so the total number of subscriptions is the\n * number of frame targets.\n */\n get subscriptionCount(): number {\n return getSubscriptionCount(this);\n }\n\n start(target: object | ListenerFuncType) {\n if (target == null) return;\n\n on(this as FrameLoop, FrameLoop.OnFrame, target);\n\n this.#requestAnimationFrame();\n\n return () => {\n this.stop(target);\n };\n }\n\n stop(target: object | ListenerFuncType) {\n if (target == null) return;\n\n off(this, FrameLoop.OnFrame, target);\n\n if (this.subscriptionCount === 0) {\n this.#cancelAnimationFrame();\n // the next target to arrive starts a fresh run of frames: its first frame is frame one and\n // reports no delta, rather than the whole length of the pause\n this.#lastNow = undefined;\n this.#frameNo = 0;\n }\n }\n\n #onFrame = (now: number) => {\n // the frame this id stands for has fired, nothing is pending any more \u2014 a target that leaves\n // during the emit below must be able to see that\n this.#rafID = 0;\n\n if (this.#isDue(now)) {\n const lastNow = this.#lastNow ?? now;\n\n this.#lastNow = now;\n this.#frameNo += 1;\n\n const frameData: FrameData = {\n now: now / 1000,\n lastNow: lastNow / 1000,\n frameNo: this.#frameNo,\n deltaTime: (now - lastNow) / 1000,\n };\n\n // `emitSafe()` rather than `emit()`, and the reason is the singleton above: one loop serves\n // every target in the realm, so a target that throws may cost neither the targets behind it\n // their frame nor the loop its next one. The plain dispatch would do both -- it ends at the\n // first listener that throws and hands the error back to the `requestAnimationFrame()`\n // callback, past the line below that asks for the frame after this one. `#rafID` is already\n // back at 0 at that point, so nothing would ever ask again and the loop would stand still\n // for the whole page without a word.\n //\n // What a target loses in exchange is the report: `emitSafe()` writes to `console.warn` and\n // hands out no error. That is the right trade here. `FrameLoop` is a singleton with no\n // `ConsoleLogger` of its own, and a frame target that throws does so at the frame rate --\n // whichever channel carries it, the first report says everything the hundredth does.\n emitSafe(this as FrameLoop, FrameLoop.OnFrame, frameData);\n }\n\n if (this.subscriptionCount > 0) {\n // a skipped frame arms again just like an emitted one, otherwise the cap would bring the\n // loop to a halt after the first frame it holds back\n this.#requestAnimationFrame();\n }\n };\n\n #isDue(now: number): boolean {\n if (this.#maxFps === 0 || this.#lastNow === undefined) return true;\n\n return now - this.#lastNow >= FrameLoop.#DueFactor * (1000 / this.#maxFps);\n }\n\n #requestAnimationFrame() {\n // idempotent: two pending frames would mean two loops, and only the second of them could ever\n // be cancelled again\n if (this.#rafID !== 0) return;\n\n this.#rafID = requestAnimationFrame(this.#onFrame);\n }\n\n #cancelAnimationFrame() {\n if (this.#rafID === 0) return;\n\n cancelAnimationFrame(this.#rafID);\n this.#rafID = 0;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAQ,UAAU,UAAU,sBAA6C,KAAK,UAAS;AAEvF,IAAI,gBAAkC;AA0B/B,MAAM,UAAU;AAAA,EACrB;AAAA,SAAO,UAAU,uBAAO,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjC,OAAO,MAAiB;AACtB,sBAAkB,IAAI,UAAU;AAChC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,OAAO,aAAa;AAAA,EAEpB,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAA+B;AAAA,EAE/B,YAAY,SAAS,GAAG;AACtB,aAAS,IAAI;AACb,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAO,KAAa;AACtB,SAAK,UAAU,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,oBAA4B;AAC9B,WAAO,qBAAqB,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,QAAmC;AACvC,QAAI,UAAU,KAAM;AAEpB,OAAG,MAAmB,UAAU,SAAS,MAAM;AAE/C,SAAK,uBAAuB;AAE5B,WAAO,MAAM;AACX,WAAK,KAAK,MAAM;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,KAAK,QAAmC;AACtC,QAAI,UAAU,KAAM;AAEpB,QAAI,MAAM,UAAU,SAAS,MAAM;AAEnC,QAAI,KAAK,sBAAsB,GAAG;AAChC,WAAK,sBAAsB;AAG3B,WAAK,WAAW;AAChB,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,WAAW,CAAC,QAAgB;AAG1B,SAAK,SAAS;AAEd,QAAI,KAAK,OAAO,GAAG,GAAG;AACpB,YAAM,UAAU,KAAK,YAAY;AAEjC,WAAK,WAAW;AAChB,WAAK,YAAY;AAEjB,YAAM,YAAuB;AAAA,QAC3B,KAAK,MAAM;AAAA,QACX,SAAS,UAAU;AAAA,QACnB,SAAS,KAAK;AAAA,QACd,YAAY,MAAM,WAAW;AAAA,MAC/B;AAcA,eAAS,MAAmB,UAAU,SAAS,SAAS;AAAA,IAC1D;AAEA,QAAI,KAAK,oBAAoB,GAAG;AAG9B,WAAK,uBAAuB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,OAAO,KAAsB;AAC3B,QAAI,KAAK,YAAY,KAAK,KAAK,aAAa,OAAW,QAAO;AAE9D,WAAO,MAAM,KAAK,YAAY,UAAU,cAAc,MAAO,KAAK;AAAA,EACpE;AAAA,EAEA,yBAAyB;AAGvB,QAAI,KAAK,WAAW,EAAG;AAEvB,SAAK,SAAS,sBAAsB,KAAK,QAAQ;AAAA,EACnD;AAAA,EAEA,wBAAwB;AACtB,QAAI,KAAK,WAAW,EAAG;AAEvB,yBAAqB,KAAK,MAAM;AAChC,SAAK,SAAS;AAAA,EAChB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arguments of {@link MicrotaskCollector.add}: a collector without a payload is asked for the
|
|
3
|
+
* key alone, one that carries a payload is asked for both.
|
|
4
|
+
*/
|
|
5
|
+
type AddArgs<K, V> = undefined extends V ? [key: K, value?: V] : [key: K, value: V];
|
|
6
|
+
/**
|
|
7
|
+
* Collects what is written in one task and hands it over a microtask later, in one batch.
|
|
8
|
+
*
|
|
9
|
+
* A second value for the same key replaces the one waiting, so a key written twice in a task
|
|
10
|
+
* reaches the delivery once, with the value that stood at the end of it.
|
|
11
|
+
*
|
|
12
|
+
* Two rules live here so that no site has to decide them again:
|
|
13
|
+
*
|
|
14
|
+
* The batch is taken out and emptied **before** the first delivery. A delivery can write to this
|
|
15
|
+
* collector -- a reader of a context value writes a context of its own, an entity that arrives
|
|
16
|
+
* while a round goes out asks for a round -- and that write belongs to the round behind this one
|
|
17
|
+
* rather than to the batch being walked. Emptying afterwards would also lose the batch to a
|
|
18
|
+
* delivery that throws: the flag is already down by then, so the next write queues a fresh
|
|
19
|
+
* microtask and hands the stale entries over a second time.
|
|
20
|
+
*
|
|
21
|
+
* Every delivery a site makes runs under `runGuarded()`. Entries that arrived in the same task are
|
|
22
|
+
* independent of one another, and one that fails costs itself and nothing behind it -- the promise
|
|
23
|
+
* `docs/api-reference.md` already spells out for context values. What counts as one delivery is
|
|
24
|
+
* the site's to say: `Entity` delivers per entry, `ComponentContext` groups its senders
|
|
25
|
+
* into rounds first and delivers per round.
|
|
26
|
+
*
|
|
27
|
+
* For a booking that has to be callable off again, see `DeferredTeardown`; for a single
|
|
28
|
+
* deferred action without a batch, `MicrotaskGate`.
|
|
29
|
+
*/
|
|
30
|
+
export declare class MicrotaskCollector<K, V = undefined> {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(deliver: (entries: Map<K, V>) => void);
|
|
33
|
+
/** Put an entry into the batch and make sure someone comes to fetch it. */
|
|
34
|
+
add(...[key, value]: AddArgs<K, V>): void;
|
|
35
|
+
/**
|
|
36
|
+
* Hand the batch over now.
|
|
37
|
+
*
|
|
38
|
+
* For a caller that needs the batch delivered at a point of its own choosing rather than
|
|
39
|
+
* whenever the microtask comes round. A queued microtask that finds nothing left returns without
|
|
40
|
+
* a delivery, so an early flush costs nothing but the ordering it was asked for.
|
|
41
|
+
*/
|
|
42
|
+
flush(): void;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=MicrotaskCollector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MicrotaskCollector.d.ts","sourceRoot":"","sources":["../../../src/utils/MicrotaskCollector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,IAAI,SAAS,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kBAAkB,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS;;IAM9C,YAAY,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAEhD;IAED,2EAA2E;IAC3E,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAOxC;IAED;;;;;;OAMG;IACH,KAAK,IAAI,IAAI,CASZ;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class MicrotaskCollector {
|
|
2
|
+
#entries = /* @__PURE__ */ new Map();
|
|
3
|
+
#scheduled = false;
|
|
4
|
+
#deliver;
|
|
5
|
+
constructor(deliver) {
|
|
6
|
+
this.#deliver = deliver;
|
|
7
|
+
}
|
|
8
|
+
/** Put an entry into the batch and make sure someone comes to fetch it. */
|
|
9
|
+
add(...[key, value]) {
|
|
10
|
+
this.#entries.set(key, value);
|
|
11
|
+
if (this.#scheduled) return;
|
|
12
|
+
this.#scheduled = true;
|
|
13
|
+
queueMicrotask(() => this.flush());
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Hand the batch over now.
|
|
17
|
+
*
|
|
18
|
+
* For a caller that needs the batch delivered at a point of its own choosing rather than
|
|
19
|
+
* whenever the microtask comes round. A queued microtask that finds nothing left returns without
|
|
20
|
+
* a delivery, so an early flush costs nothing but the ordering it was asked for.
|
|
21
|
+
*/
|
|
22
|
+
flush() {
|
|
23
|
+
this.#scheduled = false;
|
|
24
|
+
if (this.#entries.size === 0) return;
|
|
25
|
+
const entries = this.#entries;
|
|
26
|
+
this.#entries = /* @__PURE__ */ new Map();
|
|
27
|
+
this.#deliver(entries);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
MicrotaskCollector
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=MicrotaskCollector.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/utils/MicrotaskCollector.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The arguments of {@link MicrotaskCollector.add}: a collector without a payload is asked for the\n * key alone, one that carries a payload is asked for both.\n */\ntype AddArgs<K, V> = undefined extends V ? [key: K, value?: V] : [key: K, value: V];\n\n/**\n * Collects what is written in one task and hands it over a microtask later, in one batch.\n *\n * A second value for the same key replaces the one waiting, so a key written twice in a task\n * reaches the delivery once, with the value that stood at the end of it.\n *\n * Two rules live here so that no site has to decide them again:\n *\n * The batch is taken out and emptied **before** the first delivery. A delivery can write to this\n * collector -- a reader of a context value writes a context of its own, an entity that arrives\n * while a round goes out asks for a round -- and that write belongs to the round behind this one\n * rather than to the batch being walked. Emptying afterwards would also lose the batch to a\n * delivery that throws: the flag is already down by then, so the next write queues a fresh\n * microtask and hands the stale entries over a second time.\n *\n * Every delivery a site makes runs under `runGuarded()`. Entries that arrived in the same task are\n * independent of one another, and one that fails costs itself and nothing behind it -- the promise\n * `docs/api-reference.md` already spells out for context values. What counts as one delivery is\n * the site's to say: `Entity` delivers per entry, `ComponentContext` groups its senders\n * into rounds first and delivers per round.\n *\n * For a booking that has to be callable off again, see `DeferredTeardown`; for a single\n * deferred action without a batch, `MicrotaskGate`.\n */\nexport class MicrotaskCollector<K, V = undefined> {\n #entries = new Map<K, V>();\n #scheduled = false;\n\n readonly #deliver: (entries: Map<K, V>) => void;\n\n constructor(deliver: (entries: Map<K, V>) => void) {\n this.#deliver = deliver;\n }\n\n /** Put an entry into the batch and make sure someone comes to fetch it. */\n add(...[key, value]: AddArgs<K, V>): void {\n this.#entries.set(key, value as V);\n\n if (this.#scheduled) return;\n this.#scheduled = true;\n\n queueMicrotask(() => this.flush());\n }\n\n /**\n * Hand the batch over now.\n *\n * For a caller that needs the batch delivered at a point of its own choosing rather than\n * whenever the microtask comes round. A queued microtask that finds nothing left returns without\n * a delivery, so an early flush costs nothing but the ordering it was asked for.\n */\n flush(): void {\n this.#scheduled = false;\n\n if (this.#entries.size === 0) return;\n\n const entries = this.#entries;\n this.#entries = new Map();\n\n this.#deliver(entries);\n }\n}\n"],
|
|
5
|
+
"mappings": "AA8BO,MAAM,mBAAqC;AAAA,EAChD,WAAW,oBAAI,IAAU;AAAA,EACzB,aAAa;AAAA,EAEJ;AAAA,EAET,YAAY,SAAuC;AACjD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,OAAO,CAAC,KAAK,KAAK,GAAwB;AACxC,SAAK,SAAS,IAAI,KAAK,KAAU;AAEjC,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAElB,mBAAe,MAAM,KAAK,MAAM,CAAC;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAc;AACZ,SAAK,aAAa;AAElB,QAAI,KAAK,SAAS,SAAS,EAAG;AAE9B,UAAM,UAAU,KAAK;AACrB,SAAK,WAAW,oBAAI,IAAI;AAExB,SAAK,SAAS,OAAO;AAAA,EACvB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one action a microtask after it was asked for, however often it is asked for.
|
|
3
|
+
*
|
|
4
|
+
* The flag falls before the action rather than behind it: an action that asks again books a fresh
|
|
5
|
+
* round instead of writing into a flag the line behind it would clear.
|
|
6
|
+
*
|
|
7
|
+
* No batch and no guard: there is one action, so there are no neighbours a failure could cost --
|
|
8
|
+
* what it throws goes where an uncaught microtask goes. A gate that carries entries is
|
|
9
|
+
* `MicrotaskCollector`, a booking that can be called off again is `DeferredTeardown`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MicrotaskGate {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(run: () => void);
|
|
14
|
+
/** Ask for the action. */
|
|
15
|
+
schedule(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=MicrotaskGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MicrotaskGate.d.ts","sourceRoot":"","sources":["../../../src/utils/MicrotaskGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,aAAa;;IAKxB,YAAY,GAAG,EAAE,MAAM,IAAI,EAE1B;IAED,0BAA0B;IAC1B,QAAQ,IAAI,IAAI,CAQf;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class MicrotaskGate {
|
|
2
|
+
#scheduled = false;
|
|
3
|
+
#run;
|
|
4
|
+
constructor(run) {
|
|
5
|
+
this.#run = run;
|
|
6
|
+
}
|
|
7
|
+
/** Ask for the action. */
|
|
8
|
+
schedule() {
|
|
9
|
+
if (this.#scheduled) return;
|
|
10
|
+
this.#scheduled = true;
|
|
11
|
+
queueMicrotask(() => {
|
|
12
|
+
this.#scheduled = false;
|
|
13
|
+
this.#run();
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
MicrotaskGate
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=MicrotaskGate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/utils/MicrotaskGate.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Runs one action a microtask after it was asked for, however often it is asked for.\n *\n * The flag falls before the action rather than behind it: an action that asks again books a fresh\n * round instead of writing into a flag the line behind it would clear.\n *\n * No batch and no guard: there is one action, so there are no neighbours a failure could cost --\n * what it throws goes where an uncaught microtask goes. A gate that carries entries is\n * `MicrotaskCollector`, a booking that can be called off again is `DeferredTeardown`.\n */\nexport class MicrotaskGate {\n #scheduled = false;\n\n readonly #run: () => void;\n\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Ask for the action. */\n schedule(): void {\n if (this.#scheduled) return;\n this.#scheduled = true;\n\n queueMicrotask(() => {\n this.#scheduled = false;\n this.#run();\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "AAUO,MAAM,cAAc;AAAA,EACzB,aAAa;AAAA,EAEJ;AAAA,EAET,YAAY,KAAiB;AAC3B,SAAK,OAAO;AAAA,EACd;AAAA;AAAA,EAGA,WAAiB;AACf,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAElB,mBAAe,MAAM;AACnB,WAAK,aAAa;AAClB,WAAK,KAAK;AAAA,IACZ,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/utils/array-utils.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Add a new item to the array, but only if it does not already exist\n */\nexport function appendTo<T = unknown>(array: T[], item: T) {\n const index = array.indexOf(item);\n if (index === -1) {\n array.push(item);\n }\n}\n\n/**\n * Append a new item to the end of the array.\n * If the item exists, remove the previous item first.\n */\nexport function appendToEnd<T = unknown>(array: T[], item: T) {\n const index = array.indexOf(item);\n if (index !== -1) {\n array.splice(index, 1);\n }\n array.push(item);\n}\n\nexport function removeFrom<T = unknown>(array: T[], item: T) {\n const index = array.indexOf(item);\n if (index !== -1) {\n array.splice(index, 1);\n }\n}"],
|
|
4
|
+
"sourcesContent": ["/**\n * Add a new item to the array, but only if it does not already exist\n */\nexport function appendTo<T = unknown>(array: T[], item: T) {\n const index = array.indexOf(item);\n if (index === -1) {\n array.push(item);\n }\n}\n\n/**\n * Append a new item to the end of the array.\n * If the item exists, remove the previous item first.\n */\nexport function appendToEnd<T = unknown>(array: T[], item: T) {\n const index = array.indexOf(item);\n if (index !== -1) {\n array.splice(index, 1);\n }\n array.push(item);\n}\n\nexport function removeFrom<T = unknown>(array: T[], item: T) {\n const index = array.indexOf(item);\n if (index !== -1) {\n array.splice(index, 1);\n }\n}\n"],
|
|
5
5
|
"mappings": "AAGO,SAAS,SAAsB,OAAY,MAAS;AACzD,QAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,MAAI,UAAU,IAAI;AAChB,UAAM,KAAK,IAAI;AAAA,EACjB;AACF;AAMO,SAAS,YAAyB,OAAY,MAAS;AAC5D,QAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,MAAI,UAAU,IAAI;AAChB,UAAM,OAAO,OAAO,CAAC;AAAA,EACvB;AACA,QAAM,KAAK,IAAI;AACjB;AAEO,SAAS,WAAwB,OAAY,MAAS;AAC3D,QAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,MAAI,UAAU,IAAI;AAChB,UAAM,OAAO,OAAO,CAAC;AAAA,EACvB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
export declare const readNamespaceAttribute: (el: HTMLElement) => import("../types.js").NamespaceType;
|
|
2
2
|
export declare const readBooleanAttribute: (el: HTMLElement, name: string) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* The attribute read as a number, or `undefined` where the element does not carry it.
|
|
5
|
+
* `Number()` does the reading, so a value that is not a number reads as `NaN` while an empty
|
|
6
|
+
* or blank one reads as `0` — whoever asks decides what either of those means.
|
|
7
|
+
*/
|
|
8
|
+
export declare const readNumberAttribute: (el: HTMLElement, name: string) => number | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* The attribute read as a list of words: split on commas and whitespace, blanks dropped. A
|
|
11
|
+
* missing attribute is an empty list, and so is an empty or a blank one.
|
|
12
|
+
*/
|
|
13
|
+
export declare const readListAttribute: (el: HTMLElement, name: string) => string[];
|
|
3
14
|
//# sourceMappingURL=attr-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attr-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/attr-utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"attr-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/attr-utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,OAAQ,WAAW,wCAAuD,CAAC;AAE9G,eAAO,MAAM,oBAAoB,OAAQ,WAAW,QAAQ,MAAM,YAMjE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,OAAQ,WAAW,QAAQ,MAAM,KAAG,MAAM,GAAG,SACV,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,OAAQ,WAAW,QAAQ,MAAM,KAAG,MAAM,EACW,CAAC"}
|
package/src/utils/attr-utils.js
CHANGED
|
@@ -9,8 +9,12 @@ const readBooleanAttribute = (el, name) => {
|
|
|
9
9
|
}
|
|
10
10
|
return false;
|
|
11
11
|
};
|
|
12
|
+
const readNumberAttribute = (el, name) => el.hasAttribute(name) ? Number(el.getAttribute(name)) : void 0;
|
|
13
|
+
const readListAttribute = (el, name) => (el.getAttribute(name) ?? "").split(/[\s,]+/).filter((entry) => entry.length > 0);
|
|
12
14
|
export {
|
|
13
15
|
readBooleanAttribute,
|
|
14
|
-
|
|
16
|
+
readListAttribute,
|
|
17
|
+
readNamespaceAttribute,
|
|
18
|
+
readNumberAttribute
|
|
15
19
|
};
|
|
16
20
|
//# sourceMappingURL=attr-utils.js.map
|