@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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/view/ShadowEnv.ts"],
|
|
4
|
-
"sourcesContent": ["import {emit, off, on, onceAsync, Priority, retain, retainClear} from '@spearwolf/eventize';\nimport {createEffect, createSignal, destroyObjectSignals, findObjectSignalByName} from '@spearwolf/signalize';\nimport {signal} from '@spearwolf/signalize/decorators';\nimport type {MessageToViewEvent} from '../shadow-objects.js';\nimport type {ChangeTrailType, NamespaceType} from '../types.js';\nimport {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {ComponentContext} from './ComponentContext.js';\nimport type {IShadowObjectEnvProxy} from './IShadowObjectEnvProxy.js';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __shadowEnvs: Map<NamespaceType, ShadowEnv> | undefined;\n}\n\n/**\n * The reason every pending {@link ShadowEnv.ready} and {@link ShadowEnv.syncWait} promise\n * is rejected with when the environment is destroyed.\n */\nexport class ShadowEnvDestroyedError extends Error {\n constructor(message = 'the shadow environment has been destroyed') {\n super(message);\n this.name = 'ShadowEnvDestroyedError';\n }\n}\n\nexport class ShadowEnv {\n static AfterSync = 'afterSync';\n static ContextLost = 'contextLost';\n static ContextCreated = 'contextCreated';\n static ProxyFailed = 'proxyFailed';\n\n static get(ns: NamespaceType): ShadowEnv | undefined {\n if (ns == null) return undefined;\n return globalThis.__shadowEnvs?.get(ns);\n }\n\n #comCtx?: ComponentContext;\n #shaObjEnvProxy?: IShadowObjectEnvProxy;\n #syncScheduled = false;\n #syncAfterContextCreated = false;\n #syncWaitForConfirmation = false;\n #afterNextSync?: Promise<ChangeTrailType>;\n\n readonly logger = new ConsoleLogger('ShadowEnv');\n\n readonly ns$ = createSignal<NamespaceType | undefined>();\n\n @signal() accessor viewReady = false;\n @signal() accessor proxyReady = false;\n\n #isDestroyed = false;\n\n get isDestroyed() {\n return this.#isDestroyed;\n }\n\n constructor() {\n const self = this as ShadowEnv;\n retain(self, ShadowEnv.ContextCreated);\n\n on(self, ShadowEnv.ContextLost, Priority.AAA, () => {\n retainClear(self, ShadowEnv.ContextCreated);\n });\n\n createEffect(() => {\n if (this.viewReady && this.proxyReady) {\n this.view!.reCreateChanges();\n emit(self, ShadowEnv.ContextCreated, self);\n if (this.#syncAfterContextCreated) {\n this.#syncAfterContextCreated = false;\n this.#syncNow();\n }\n return () => {\n emit(self, ShadowEnv.ContextLost, self);\n };\n }\n // the two @signal accessors above create their signals during field initialization,\n // so both lookups resolve by the time the constructor body runs\n }, [findObjectSignalByName(this, 'viewReady')!, findObjectSignalByName(this, 'proxyReady')!]);\n }\n\n get view(): ComponentContext | undefined {\n return this.#comCtx;\n }\n\n set view(ctx: ComponentContext | null | undefined) {\n if (ctx !== this.#comCtx) {\n if (this.#comCtx?.ns && globalThis.__shadowEnvs) {\n globalThis.__shadowEnvs.delete(this.#comCtx.ns);\n }\n\n this.#comCtx = ctx ?? undefined;\n\n if (this.#comCtx?.ns) {\n globalThis.__shadowEnvs ??= new Map();\n if (globalThis.__shadowEnvs.has(this.#comCtx.ns) && globalThis.__shadowEnvs.get(this.#comCtx.ns) !== this) {\n if (this.logger.isWarn) {\n this.logger.warn(\n 'overwrite a namespace already in use',\n this.#comCtx.ns,\n globalThis.__shadowEnvs.get(this.#comCtx.ns),\n );\n }\n }\n globalThis.__shadowEnvs.set(this.#comCtx.ns, this);\n }\n\n this.viewReady = Boolean(ctx);\n }\n }\n\n get envProxy(): IShadowObjectEnvProxy | undefined {\n return this.#shaObjEnvProxy;\n }\n\n set envProxy(proxy: IShadowObjectEnvProxy | null | undefined) {\n if (proxy !== this.#shaObjEnvProxy) {\n const prevProxy = this.#shaObjEnvProxy;\n this.#shaObjEnvProxy = proxy ?? undefined;\n\n if (this.#shaObjEnvProxy) {\n this.#shaObjEnvProxy.onMessageToView = this.#onMessageToView.bind(this);\n this.#shaObjEnvProxy.onProxyFailed = this.#onProxyFailed.bind(this);\n }\n\n if (prevProxy) {\n prevProxy.destroy();\n }\n\n this.proxyReady = false;\n\n proxy\n ?.start()\n .then(() => {\n this.proxyReady = true;\n })\n .catch((error) => {\n this.logger.error('failed to start envProxy', error);\n this.proxyReady = false;\n });\n }\n }\n\n get isReady(): boolean {\n return Boolean(this.#comCtx && this.#shaObjEnvProxy && this.proxyReady && !this.isDestroyed);\n }\n\n #whenDestroyed?: Promise<never>;\n #rejectWhenDestroyed?: (error: Error) => void;\n\n /**\n * A promise that rejects the moment this environment is destroyed.\n *\n * Every promise the public API hands out races against it. Without that race a caller\n * would wait forever, because {@link ShadowEnv.destroy} tears down the very listeners\n * those promises are built on.\n */\n #destroyedSignal(): Promise<never> {\n if (this.#whenDestroyed == null) {\n this.#whenDestroyed = new Promise<never>((_, reject) => {\n this.#rejectWhenDestroyed = reject;\n });\n // the signal is rejected unconditionally, also when nobody happens to be racing against it\n this.#whenDestroyed.catch(() => {});\n }\n return this.#whenDestroyed;\n }\n\n /**\n * Resolves once the environment is ready.\n *\n * @throws {ShadowEnvDestroyedError} if the environment is destroyed before that happens\n */\n readonly ready = async (): Promise<ShadowEnv> => {\n if (this.#isDestroyed) throw new ShadowEnvDestroyedError();\n if (this.isReady) return this;\n return Promise.race([onceAsync<ShadowEnv>(this as ShadowEnv, ShadowEnv.ContextCreated), this.#destroyedSignal()]);\n };\n\n sync(): void {\n if (this.#isDestroyed) return;\n if (!this.isReady) {\n this.#syncAfterContextCreated = true;\n return;\n }\n if (this.#syncScheduled) return;\n this.#syncScheduled = true;\n queueMicrotask(this.#syncIfScheduled);\n }\n\n /**\n * Like {@link ShadowEnv.sync}, but resolves with the change trail once the cycle completed.\n *\n * @throws {ShadowEnvDestroyedError} if the environment is destroyed before that happens\n */\n syncWait(): Promise<ChangeTrailType> {\n if (this.#isDestroyed) return Promise.reject(new ShadowEnvDestroyedError());\n\n this.#syncWaitForConfirmation = true;\n this.sync();\n\n if (this.#afterNextSync) return this.#afterNextSync;\n\n this.#afterNextSync = Promise.race([\n onceAsync<ChangeTrailType>(this as ShadowEnv, ShadowEnv.AfterSync),\n this.#destroyedSignal(),\n ]).then(\n (changeTrail) => {\n this.#afterNextSync = undefined;\n return changeTrail;\n },\n (error) => {\n this.#afterNextSync = undefined;\n throw error;\n },\n );\n\n return this.#afterNextSync;\n }\n\n /**\n * Tear the environment down: the proxy is destroyed, the namespace is released, all signals\n * and listeners are removed, and every caller still waiting on {@link ShadowEnv.ready} or\n * {@link ShadowEnv.syncWait} is rejected with a {@link ShadowEnvDestroyedError} instead of\n * being left pending forever. Calling it more than once is a no-op.\n */\n destroy() {\n if (this.#isDestroyed) return;\n\n // set first: isReady must report false for everything that runs below\n this.#isDestroyed = true;\n\n this.#syncScheduled = false;\n this.#syncAfterContextCreated = false;\n this.#syncWaitForConfirmation = false;\n\n const ns = this.#comCtx?.ns;\n\n // the `envProxy` setter destroys the previous proxy, so it must not be destroyed here as well\n this.envProxy = undefined;\n this.view = undefined;\n\n const shadowEnvs = globalThis.__shadowEnvs;\n if (ns && shadowEnvs?.get(ns) === this) {\n shadowEnvs.delete(ns);\n }\n\n // settle everyone still waiting before the listeners they depend on are removed\n this.#rejectWhenDestroyed?.(new ShadowEnvDestroyedError());\n this.#afterNextSync = undefined;\n\n destroyObjectSignals(this);\n off(this);\n\n Object.freeze(this);\n }\n\n #syncIfScheduled = () => {\n if (this.#syncScheduled) {\n this.#syncNow();\n }\n };\n\n async #syncNow() {\n this.#syncScheduled = false;\n\n if (this.#isDestroyed) return;\n\n if (!this.isReady) {\n // the environment went away between scheduling and running this sync:\n // re-arm instead of dropping it, otherwise a pending syncWait() would never settle\n this.#syncAfterContextCreated = true;\n return;\n }\n\n const data = this.view!.buildChangeTrails();\n\n const waitForConfirmation = this.#syncWaitForConfirmation;\n this.#syncWaitForConfirmation = false;\n\n try {\n if (data.length > 0) {\n await this.envProxy!.applyChangeTrail(data, waitForConfirmation);\n }\n } catch (error) {\n this.logger.error('failed to apply change trail', error);\n } finally {\n // always emitted, even for an empty change trail, so that syncWait() settles on every cycle\n emit(this as ShadowEnv, ShadowEnv.AfterSync, data);\n }\n }\n\n #onMessageToView(event: Omit<MessageToViewEvent, 'transferables'>) {\n if (this.logger.isDebug) {\n this.logger.debug('onMessageToView', event.type, event.data);\n }\n this.view?.dispatchMessage(event.uuid, event.type, event.data, event.traverseChildren);\n }\n\n #onProxyFailed(reason: unknown) {\n // destroy() freezes this instance and destroys its signals; a proxy may report its failure afterwards\n if (this.#isDestroyed) return;\n\n this.logger.error('the environment proxy failed', reason);\n\n try {\n // the reason before the consequence: ContextLost follows from dropping proxyReady\n emit(this as ShadowEnv, ShadowEnv.ProxyFailed, reason, this as ShadowEnv);\n } finally {\n // in the `finally`, because losing the environment is not up for debate:\n // a listener that throws must not leave `isReady` claiming otherwise\n this.proxyReady = false;\n }\n }\n}"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["import {emitStrict, off, on, onceAsync, Priority, retain, retainClear} from '@spearwolf/eventize';\nimport {\n createEffect,\n createSignal,\n destroyObjectSignals,\n type Effect,\n findObjectSignalByName,\n hibernate,\n} from '@spearwolf/signalize';\nimport {signal} from '@spearwolf/signalize/decorators';\nimport {ChangeTrailRefusedError} from '../ChangeTrailRefusedError.js';\nimport {GlobalNS} from '../constants.js';\nimport {createViewSnapshot} from '../inspect/createViewSnapshot.js';\nimport type {EnvSnapshot, InspectRequest, KernelSnapshot} from '../inspect/types.js';\nimport type {MessageToViewEvent} from '../shadow-objects.js';\nimport type {ChangeTrailType, NamespaceType} from '../types.js';\nimport {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {ComponentContext} from './ComponentContext.js';\nimport type {IShadowObjectEnvProxy} from './IShadowObjectEnvProxy.js';\nimport {RemoteWorkerEnv} from './RemoteWorkerEnv.js';\n\ndeclare global {\n var __shadowEnvs: Map<NamespaceType, ShadowEnv> | undefined;\n}\n\n/**\n * The reason every pending {@link ShadowEnv.ready} and {@link ShadowEnv.syncWait} promise\n * is rejected with when the environment is destroyed.\n */\nexport class ShadowEnvDestroyedError extends Error {\n constructor(message = 'the shadow environment has been destroyed') {\n super(message);\n this.name = 'ShadowEnvDestroyedError';\n }\n}\n\n/**\n * The settlement of one synchronization cycle: the promise `syncWait()` hands out, and the pair\n * that settles it. A cycle takes its own settlement with it the moment its change trail is\n * built, which is what lets two cycles be in flight at once without settling each other's\n * callers.\n */\ntype SyncCycle = {\n promise: Promise<ChangeTrailType>;\n resolve: (changeTrail: ChangeTrailType) => void;\n reject: (reason: unknown) => void;\n};\n\nconst namespaceAsString = (ns: NamespaceType | undefined): string =>\n typeof ns === 'symbol' ? (ns.description ?? String(ns)) : (ns ?? '');\n\nconst errorInfo = (error: unknown): {name: string; message: string} =>\n error instanceof Error ? {name: error.name, message: error.message} : {name: 'Error', message: String(error)};\n\n/**\n * A promise that rejects with the signal's reason the moment it aborts, and the way to unhook it.\n *\n * The abort is raced rather than left to the callee: a proxy that takes the signal and ignores it\n * would otherwise leave the caller pending for as long as it stays silent.\n */\nconst abortRace = (signal: AbortSignal): {promise: Promise<never>; dispose: () => void} => {\n let onAbort!: () => void;\n const promise = new Promise<never>((_, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, {once: true});\n });\n // an abort that arrives between the race settling and the listener going is nobody's rejection\n promise.catch(() => {});\n return {promise, dispose: () => signal.removeEventListener('abort', onAbort)};\n};\n\nconst proxyKind = (proxy: IShadowObjectEnvProxy | undefined): EnvSnapshot['kind'] => {\n if (proxy === undefined) return 'none';\n if ((proxy as {isLocalEnv?: unknown}).isLocalEnv === true) return 'local';\n if (proxy instanceof RemoteWorkerEnv) return 'worker';\n return 'custom';\n};\n\nexport class ShadowEnv {\n static AfterSync = 'afterSync';\n static SyncFailed = 'syncFailed';\n static ContextLost = 'contextLost';\n static ContextCreated = 'contextCreated';\n static ProxyFailed = 'proxyFailed';\n\n static get(ns: NamespaceType): ShadowEnv | undefined {\n if (ns == null) return undefined;\n return globalThis.__shadowEnvs?.get(ns);\n }\n\n /**\n * Every environment that holds a namespace, in registration order, each described by\n * {@link ShadowEnv.inspect}. One environment that cannot answer costs its own entry and not the\n * list: the per-environment failures are reported under `error`, and an environment destroyed\n * while it answers drops out of the list. Rejects only for the caller's reasons -- an aborted\n * signal.\n *\n * `only` narrows the list before anything is asked: it is called with the namespace an\n * environment is registered under, and an environment it refuses is neither inspected nor\n * listed. That is what lets a caller keep an environment out of a picture without touching it.\n */\n static async inspectAll(\n request: InspectRequest = {},\n signal?: AbortSignal,\n only?: (ns: NamespaceType) => boolean,\n ): Promise<EnvSnapshot[]> {\n const envs: ShadowEnv[] = [];\n for (const [ns, env] of globalThis.__shadowEnvs?.entries() ?? []) {\n if (only === undefined || only(ns)) envs.push(env);\n }\n const settled = await Promise.all(\n envs.map((env) =>\n env.inspect(request, signal).catch((error) => {\n if (signal?.aborted) throw signal.reason;\n if (error instanceof ShadowEnvDestroyedError) return undefined;\n throw error;\n }),\n ),\n );\n return settled.filter((snapshot): snapshot is EnvSnapshot => snapshot !== undefined);\n }\n\n #comCtx?: ComponentContext | undefined;\n #shaObjEnvProxy?: IShadowObjectEnvProxy | undefined;\n #syncScheduled = false;\n #syncAfterContextCreated = false;\n #syncWaitForConfirmation = false;\n #nextSyncCycle?: SyncCycle | undefined;\n\n readonly #logger = new ConsoleLogger('ShadowEnv');\n\n /** The logger this environment reports through. */\n get logger(): ConsoleLogger {\n return this.#logger;\n }\n\n /**\n * The namespace of the {@link ComponentContext} this environment observes, and `undefined`\n * while it observes none. The `view` setter writes it, so it carries the name\n * {@link ShadowEnv.get} finds this environment under, unless another environment has since\n * taken that namespace over; a `destroy()` leaves it on `undefined`.\n */\n readonly ns$ = createSignal<NamespaceType | undefined>();\n\n @signal() accessor viewReady = false;\n @signal() accessor proxyReady = false;\n\n #isDestroyed = false;\n\n get isDestroyed() {\n return this.#isDestroyed;\n }\n\n constructor() {\n const self = this as ShadowEnv;\n retain(self, ShadowEnv.ContextCreated);\n\n on(self, ShadowEnv.ContextLost, Priority.Critical, () => {\n retainClear(self, ShadowEnv.ContextCreated);\n });\n\n // the effect that reports the context comes with the first `view` or `envProxy` this\n // environment is given: an effect stands in a module-wide queue until it is destroyed, and an\n // environment that never receives either half must stay collectable\n }\n\n #contextEffect?: Effect | undefined;\n\n /**\n * Builds the effect that reports {@link ShadowEnv.ContextCreated} and {@link ShadowEnv.ContextLost}\n * once, on the first half of the environment that arrives.\n */\n #ensureContextEffect(): void {\n if (this.#isDestroyed || this.#contextEffect != null) return;\n\n const self = this as ShadowEnv;\n\n // `hibernate()` clears the effect stack for the duration, and that is not optional here. Both\n // setters below are public API and are called from application code that may well sit inside a\n // `createEffect()` -- `<shae-worker>` is one such caller: it assigns `view` from inside an\n // `ns$.onChange()` callback. An effect built while a foreign effect is running becomes that\n // effect's child and dies at its next run, taking the context reports with it. The three custom\n // elements shield their `connectedCallback` the same way.\n //\n // One observable side effect comes with it: a `batch()` the caller has open is pushed through\n // before this returns, so the effects it was holding back run at this point. The writes the\n // setter makes afterwards go back into that batch and wait for it to close, the way they would\n // without any of this.\n hibernate(() => {\n this.#contextEffect = createEffect(() => {\n if (this.viewReady && this.proxyReady) {\n // Both halves being ready is what this reacts to, and either half can be the one that\n // arrived last. A fresh proxy under the standing view is the case the recovery is written\n // for: it holds none of the uuids, and the re-created trail goes through. A view that is\n // taken off and hung back on -- `env.view = undefined; env.view = ctx`, and `get(ns)`\n // hands back the very same context -- is the one that does not: its memory is still full,\n // the proxy that stayed still holds every uuid in it, and the trail is refused at its\n // first creation and stays refused. A context that is genuinely new carries an empty\n // memory and re-creates nothing at all. Whoever swaps the view of a live environment\n // tears the proxy down with it.\n this.view!.reCreateChanges();\n this.#announceContext(ShadowEnv.ContextCreated, self);\n if (this.#syncAfterContextCreated) {\n this.#syncAfterContextCreated = false;\n this.#syncNow();\n }\n return () => {\n this.#announceContext(ShadowEnv.ContextLost, self);\n };\n }\n // the two @signal accessors above create their signals during field initialization,\n // so both lookups resolve by the time a setter runs\n }, [findObjectSignalByName(this, 'viewReady')!, findObjectSignalByName(this, 'proxyReady')!]);\n });\n }\n\n get view(): ComponentContext | undefined {\n return this.#comCtx;\n }\n\n set view(ctx: ComponentContext | null | undefined) {\n if (ctx !== this.#comCtx) {\n if (ctx) this.#ensureContextEffect();\n\n this.#releaseNamespace(this.#comCtx?.ns);\n\n this.#comCtx = ctx ?? undefined;\n\n if (this.#comCtx?.ns) {\n globalThis.__shadowEnvs ??= new Map();\n if (globalThis.__shadowEnvs.has(this.#comCtx.ns) && globalThis.__shadowEnvs.get(this.#comCtx.ns) !== this) {\n this.logger.warn('overwrite a namespace already in use', this.#comCtx.ns, globalThis.__shadowEnvs.get(this.#comCtx.ns));\n }\n globalThis.__shadowEnvs.set(this.#comCtx.ns, this);\n }\n\n // the namespace this environment observes, published where the name promises it. `view` is\n // the only way one reaches this object -- `ComponentContext.ns` is assigned in its\n // constructor and never again -- and the write stands behind the registration above, so\n // whoever reacts to it finds `ShadowEnv.get()` already answering this environment. It needs\n // no truthiness guard of its own: `toNamespace()` turns an empty or whitespace-only string\n // into `GlobalNS`, so a context that exists has a namespace that registers.\n this.ns$.set(this.#comCtx?.ns);\n\n this.viewReady = Boolean(ctx);\n }\n }\n\n /**\n * Releases the namespace registration, but only while this environment holds it. A namespace\n * carries one environment at a time, and an assignment that displaces another one leaves that\n * other environment registered under nothing -- taking its entry along on the way out would\n * make `ShadowEnv.get()` answer `undefined` for an environment that is very much alive.\n */\n #releaseNamespace(ns: NamespaceType | undefined): void {\n // the same truthiness check the `view` setter above uses before it registers a namespace,\n // so release and registration recognize the same namespace as \"none\"\n if (!ns) return;\n const shadowEnvs = globalThis.__shadowEnvs;\n if (shadowEnvs?.get(ns) === this) {\n shadowEnvs.delete(ns);\n }\n }\n\n get envProxy(): IShadowObjectEnvProxy | undefined {\n return this.#shaObjEnvProxy;\n }\n\n // Each assignment to `envProxy` opens a generation. A start that finishes outside the generation\n // it belongs to speaks for a proxy this environment has already let go, and is discarded.\n #proxyGeneration = 0;\n\n set envProxy(proxy: IShadowObjectEnvProxy | null | undefined) {\n if (proxy !== this.#shaObjEnvProxy) {\n if (proxy) this.#ensureContextEffect();\n\n const prevProxy = this.#shaObjEnvProxy;\n this.#shaObjEnvProxy = proxy ?? undefined;\n\n const generation = ++this.#proxyGeneration;\n\n if (this.#shaObjEnvProxy) {\n this.#shaObjEnvProxy.onMessageToView = this.#onMessageToView.bind(this);\n this.#shaObjEnvProxy.onProxyFailed = this.#onProxyFailed.bind(this);\n }\n\n if (prevProxy) {\n prevProxy.destroy();\n\n // a proxy this environment has let go speaks for an environment that is gone; whatever\n // it makes of its own failure from here on is no longer this environment's business\n prevProxy.onProxyFailed = undefined;\n\n // one microtask later, not synchronously: a local environment hands a message an\n // `onDestroy` sends towards the view to a microtask queued while `destroy()` runs, and\n // that message is still addressed to this environment. The microtask queue is served in\n // the order it was filled, so every message the teardown queued runs ahead of this line\n // -- and the released proxy falls silent for everything after it\n queueMicrotask(() => {\n prevProxy.onMessageToView = undefined;\n });\n }\n\n this.proxyReady = false;\n\n // the catch stays behind the then: a listener of ContextCreated that throws is reported here,\n // and turning this into `then(onFulfilled, onRejected)` would let it escape as an unhandled rejection\n proxy\n ?.start()\n .then(() => {\n if (generation !== this.#proxyGeneration) return;\n this.proxyReady = true;\n })\n .catch((error) => {\n if (generation !== this.#proxyGeneration) return;\n this.logger.error('failed to start envProxy', error);\n this.proxyReady = false;\n });\n }\n }\n\n get isReady(): boolean {\n return Boolean(this.#comCtx && this.#shaObjEnvProxy && this.proxyReady && !this.isDestroyed);\n }\n\n #whenDestroyed?: Promise<never>;\n #rejectWhenDestroyed?: (error: Error) => void;\n\n /**\n * A promise that rejects the moment this environment is destroyed.\n *\n * Every promise the public API hands out races against it. Without that race a caller\n * would wait forever, because {@link ShadowEnv.destroy} tears down the very listeners\n * those promises are built on.\n */\n #destroyedSignal(): Promise<never> {\n if (this.#whenDestroyed == null) {\n this.#whenDestroyed = new Promise<never>((_, reject) => {\n this.#rejectWhenDestroyed = reject;\n });\n // the signal is rejected unconditionally, also when nobody happens to be racing against it\n this.#whenDestroyed.catch(() => {});\n }\n return this.#whenDestroyed;\n }\n\n /**\n * Resolves once the environment is ready.\n *\n * @throws {ShadowEnvDestroyedError} if the environment is destroyed before that happens\n */\n readonly ready = async (): Promise<ShadowEnv> => {\n if (this.#isDestroyed) throw new ShadowEnvDestroyedError();\n if (this.isReady) return this;\n return Promise.race([onceAsync<ShadowEnv>(this as ShadowEnv, ShadowEnv.ContextCreated), this.#destroyedSignal()]);\n };\n\n /**\n * Send the change trail without asking the Shadow Environment to confirm it --\n * {@link ShadowEnv.syncWait} is the call that asks.\n *\n * What becomes of a refusal is then the proxy's decision, and the two shipped ones differ.\n * `LocalShadowObjectEnv` runs the Kernel synchronously, inside the call the environment makes to\n * the proxy -- one microtask after `sync()` returned, not inside the call to `sync()` itself --\n * and rejects with what it threw, so a refusal reaches {@link ShadowEnv.SyncFailed} here as well,\n * carrying its number.\n * `RemoteWorkerEnv` sends no serial without a confirmation and gets no answer back, so a\n * refusal stays in the worker: it is written to the console there, `SyncFailed` stays silent, and\n * the whole trail is booked as applied.\n *\n * {@link ShadowEnv.syncWait} is the way both proxies answer on.\n */\n sync(): void {\n if (this.#isDestroyed) return;\n if (!this.isReady) {\n this.#syncAfterContextCreated = true;\n return;\n }\n if (this.#syncScheduled) return;\n this.#syncScheduled = true;\n queueMicrotask(this.#syncIfScheduled);\n }\n\n /**\n * Like {@link ShadowEnv.sync}, but resolves with the change trail once the cycle completed.\n *\n * A cycle whose change trail the Shadow Environment could not apply rejects instead, with the\n * reason the proxy gave. Where the Kernel itself refused the trail that reason is a\n * {@link ChangeTrailRefusedError}: it names how many entries the Kernel applied, this side folds\n * exactly those into its bookkeeping, and everything behind that line stays pending and goes out\n * again with the next cycle. A trail whose cause of refusal stays put is refused every time, which\n * is what {@link ShadowEnv.SyncFailed} is the place to act on.\n *\n * A reason that says nothing about how far the Kernel got -- a confirmation window that ran out,\n * a proxy whose environment is gone -- counts the whole trail as applied, because a Shadow\n * Environment that fell silent may well hold all of it. Then the trail is gone, and only a\n * re-creation from the Component Memory brings it back. That re-creation belongs to a fresh\n * proxy: an environment that still holds the entities refuses a creation for a uuid it already\n * has. Handing {@link ShadowEnv.envProxy} a new proxy is therefore the way back -- the Shadow\n * Environment calls {@link ComponentContext.reCreateChanges} itself once the new proxy is ready.\n * Making that call is the consumer's decision, the same way recovering from a\n * {@link ShadowEnv.ProxyFailed} is.\n *\n * Which cycle a caller gets is decided when the change trail is built. Everyone who arrives\n * before that point waits on the same promise -- they all ride the same trail. From the build\n * on the trail is fixed, and a call after it belongs to the next cycle, the one that carries\n * the changes made since. That holds inside a listener of {@link ShadowEnv.AfterSync} or\n * {@link ShadowEnv.SyncFailed} as well: the cycle it was told about is over, so the call opens\n * the one behind it.\n *\n * @throws {ShadowEnvDestroyedError} if the environment is destroyed before the cycle completes\n */\n syncWait(): Promise<ChangeTrailType> {\n if (this.#isDestroyed) return Promise.reject(new ShadowEnvDestroyedError());\n\n this.#syncWaitForConfirmation = true;\n this.sync();\n\n // Every caller that arrives before the change trail is built waits on the same promise: they\n // all ride the same trail. From the build on the trail is fixed, `#syncNow()` has taken this\n // cycle with it, and the next caller opens the one behind it -- the cycle that will carry the\n // change they are about to make.\n this.#nextSyncCycle ??= this.#openSyncCycle();\n\n return this.#nextSyncCycle.promise;\n }\n\n /**\n * Describe this environment: the View's component tree and, where the proxy is ready and\n * implements `inspect`, the Kernel's Entity Tree behind it.\n *\n * Never rejects for a reason inside the environment -- a proxy that cannot answer, a Kernel that\n * threw, a View snapshot that threw -- and reports those under `error`, with the half that failed\n * absent. Where both halves fail, `error` carries the View's reason. A proxy that is not ready yet\n * is not an error either: `state.proxyReady` says so, and `kernel` is simply absent. It rejects\n * only for a reason of the caller's: an aborted signal, or an environment that is destroyed\n * before or while the proxy answers. The signal is raced here as well, so an abort settles the\n * call whether or not the proxy honours the signal it was handed.\n *\n * A caller that wants the View and the Kernel to agree after its own change awaits\n * {@link ShadowEnv.syncWait} first; the View snapshot reads the committed Component Memory,\n * not the pending changes.\n */\n async inspect(request: InspectRequest = {}, signal?: AbortSignal): Promise<EnvSnapshot> {\n if (this.#isDestroyed) throw new ShadowEnvDestroyedError();\n if (signal?.aborted) throw signal.reason;\n\n const ns = this.#comCtx?.ns;\n const proxy = this.#shaObjEnvProxy;\n\n const snapshot: EnvSnapshot = {\n namespace: namespaceAsString(ns),\n isGlobalNamespace: ns === GlobalNS,\n kind: proxyKind(proxy),\n state: {viewReady: this.viewReady, proxyReady: this.proxyReady, isReady: this.isReady, isDestroyed: this.#isDestroyed},\n };\n\n if (this.#comCtx) {\n try {\n snapshot.view = createViewSnapshot(this.#comCtx, request);\n } catch (error) {\n snapshot.error = errorInfo(error);\n }\n }\n\n if (proxy === undefined || !this.proxyReady) return snapshot;\n\n if (typeof proxy.inspect !== 'function') {\n snapshot.error = {name: 'NotInspectable', message: 'the environment proxy does not implement inspect()'};\n return snapshot;\n }\n\n // the race is what settles a caller whose environment is destroyed, or whose signal aborts,\n // while the proxy answers\n const aborted = signal === undefined ? undefined : abortRace(signal);\n const contenders: Promise<KernelSnapshot>[] = [proxy.inspect(request, signal), this.#destroyedSignal()];\n if (aborted !== undefined) contenders.push(aborted.promise);\n\n try {\n snapshot.kernel = await Promise.race(contenders);\n } catch (error) {\n if (this.#isDestroyed) throw new ShadowEnvDestroyedError();\n if (signal?.aborted) throw signal.reason;\n // a View that already failed keeps the field: it is the half the caller can still act on\n if (snapshot.error === undefined) snapshot.error = errorInfo(error);\n } finally {\n aborted?.dispose();\n }\n\n return snapshot;\n }\n\n /**\n * The cycle settles this promise by hand rather than through `AfterSync` / `SyncFailed`. A\n * subscription made here would stand in line behind the listeners the application registered\n * during setup, and an eventize emit stops at the first listener that throws -- everything\n * behind it, this promise included, would be left waiting forever. Settling by hand also\n * spares the bookkeeping a subscription per call would need: a cycle produces one of the two\n * events, and the subscriber of the other one would stay behind and pile up.\n */\n #openSyncCycle(): SyncCycle {\n let resolve!: (changeTrail: ChangeTrailType) => void;\n let reject!: (reason: unknown) => void;\n\n const outcome = new Promise<ChangeTrailType>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n // the race is what settles a caller whose environment is destroyed before the cycle ends:\n // `destroy()` tears down the very listeners any other route would depend on\n return {promise: Promise.race([outcome, this.#destroyedSignal()]), resolve, reject};\n }\n\n /**\n * Tear the environment down: the proxy is destroyed, the namespace is released, all signals\n * and listeners are removed, and every caller still waiting on {@link ShadowEnv.ready} or\n * {@link ShadowEnv.syncWait} is rejected with a {@link ShadowEnvDestroyedError} instead of\n * being left pending forever. Calling it more than once is a no-op.\n */\n destroy() {\n if (this.#isDestroyed) return;\n\n // set first: isReady must report false for everything that runs below\n this.#isDestroyed = true;\n\n this.#syncScheduled = false;\n this.#syncAfterContextCreated = false;\n this.#syncWaitForConfirmation = false;\n\n // the `envProxy` setter destroys the previous proxy, so it must not be destroyed here as well\n this.envProxy = undefined;\n // the `view` setter releases the namespace registration on the way out, ownership-checked, and clears `ns$` with it\n this.view = undefined;\n\n // settle everyone still waiting before the listeners they depend on are removed\n this.#rejectWhenDestroyed?.(new ShadowEnvDestroyedError());\n this.#nextSyncCycle = undefined;\n\n // The effect is built here, so it is released here: its lifetime hangs on this class rather\n // than on what a reactivity library makes of an effect whose dependencies are taken away.\n // `ContextLost` goes out exactly once, from whichever of the two gets there first: the effect\n // rerun that `this.envProxy = undefined` triggers above, or -- while an open `batch()` parks\n // that drop of `proxyReady` -- the destroy on this line. A cleanup function belongs to the run\n // that returned it, and is spent by the one that runs it.\n this.#contextEffect?.destroy();\n this.#contextEffect = undefined;\n\n destroyObjectSignals(this);\n off(this);\n\n Object.freeze(this);\n }\n\n #syncIfScheduled = () => {\n if (this.#syncScheduled) {\n this.#syncNow();\n }\n };\n\n async #syncNow() {\n this.#syncScheduled = false;\n\n if (this.#isDestroyed) return;\n\n if (!this.isReady) {\n // the environment went away between scheduling and running this sync:\n // re-arm instead of dropping it, otherwise a pending syncWait() would never settle\n this.#syncAfterContextCreated = true;\n return;\n }\n\n const data = this.view!.buildChangeTrails(false);\n\n // The trail is fixed from here on, and with it the set of callers this cycle answers: it\n // leaves holding `#nextSyncCycle`, and a `syncWait()` from now on opens the cycle behind it.\n // The settlement travels in this frame rather than in a field, because two cycles can be in\n // flight at once -- `ComponentContext.buildChangeTrails()` says as much at its own end of this.\n const cycle = this.#nextSyncCycle;\n this.#nextSyncCycle = undefined;\n\n const waitForConfirmation = this.#syncWaitForConfirmation;\n this.#syncWaitForConfirmation = false;\n\n try {\n if (data.length > 0) {\n await this.envProxy!.applyChangeTrail(data, waitForConfirmation);\n }\n } catch (error) {\n // an environment that was torn down while its trail was in flight ends its cycle in silence:\n // destroy() has already rejected whoever waited on it and taken the listeners off, and a proxy\n // that refuses because it is being destroyed is not a failure anybody needs reported. The same\n // guard `#onProxyFailed()` carries at the neighbouring spot.\n if (this.#isDestroyed) return;\n\n // the log entry before anything else: what went wrong is on the record even if the report\n // of it runs into a listener that cannot cope\n this.logger.error('failed to apply change trail', error);\n this.#commitSyncCycle(data, error);\n this.#endSyncCycle(data, cycle, {reason: error});\n return;\n }\n\n if (this.#isDestroyed) return;\n\n // an empty change trail ends here as well -- nothing is sent, so nothing can be refused\n this.#commitSyncCycle(data);\n this.#endSyncCycle(data, cycle);\n }\n\n /**\n * Draws the line between what the Shadow Environment applied and what it still owes, and hands\n * it to the view. Runs ahead of {@link ShadowEnv.#endSyncCycle} so that a `SyncFailed` listener\n * and a waiting {@link ShadowEnv.syncWait} caller find bookkeeping that already holds.\n *\n * An empty change trail is settled as well: the build may have retired components even without\n * writing an entry for any of them.\n */\n #commitSyncCycle(changeTrail: ChangeTrailType, reason?: unknown): void {\n // A reason that does not say how far the Kernel got says nothing about the trail either: a\n // confirmation window that ran out leaves an environment that may well have applied every\n // entry, and a creation re-sent for an entity it already holds is refused -- a trail kept\n // pending on a guess would come back to that refusal cycle after cycle. The line moves only\n // where the Kernel itself named the count.\n const appliedCount = reason instanceof ChangeTrailRefusedError ? reason.appliedCount : changeTrail.length;\n\n // `?.` rather than `!`: the view can be taken off -- `env.view = undefined` -- between the\n // await above and this line\n this.view?.commitChangeTrail(appliedCount, changeTrail);\n }\n\n /**\n * Sends one of the two context events and keeps a listener that throws to itself.\n *\n * Both go out from inside the effect that watches `viewReady` and `proxyReady` -- `ContextLost`\n * from its cleanup -- so a throw would leave through whatever signal write set that effect\n * going, and reach code that has nothing to do with either event. `emitStrict()` serves every\n * listener whatever the one before it did, and what they threw is reported here and travels no\n * further.\n */\n #announceContext(eventName: string, self: ShadowEnv): void {\n try {\n emitStrict(self, eventName, self);\n } catch (error) {\n this.logger.error(`a ${eventName} listener threw`, error);\n }\n }\n\n /**\n * Ends a synchronization cycle in exactly one of its two outcomes. A cycle the Shadow\n * Environment applied resolves {@link ShadowEnv.syncWait} and emits {@link ShadowEnv.AfterSync};\n * a cycle whose change trail it refused rejects and emits {@link ShadowEnv.SyncFailed}. Only a\n * listener that can tell the two apart can react to either, so no cycle ever sends both.\n *\n * The waiting caller is settled before the event goes out, and the emit is the last thing that\n * happens: a settlement that travelled as a listener of its own would sit behind whatever the\n * application registered during setup, and this method is the wrong place to make a promise\n * depend on the order consumers subscribe in. The dispatch is `emitStrict()`, so every listener\n * hears about the cycle whatever the one before it did, and what they threw arrives here\n * afterwards -- one of them unchanged, several as an `AggregateError` in dispatch order. It ends\n * here as well: `#syncNow()` runs unawaited, and an error escaping it becomes an unhandled\n * rejection rather than a report anybody reads.\n *\n * Which cycle is being ended arrives as an argument: the caller carries it from the moment its\n * change trail was built, so a cycle that is still in flight cannot be settled by the one behind it.\n */\n #endSyncCycle(changeTrail: ChangeTrailType, cycle: SyncCycle | undefined, failure?: {reason: unknown}) {\n try {\n if (failure) {\n cycle?.reject(failure.reason);\n emitStrict(this as ShadowEnv, ShadowEnv.SyncFailed, failure.reason, changeTrail, this as ShadowEnv);\n } else {\n cycle?.resolve(changeTrail);\n emitStrict(this as ShadowEnv, ShadowEnv.AfterSync, changeTrail);\n }\n } catch (error) {\n this.logger.error('a sync cycle listener threw', error);\n }\n }\n\n #onMessageToView(event: Omit<MessageToViewEvent, 'transferables'>) {\n this.logger.debug('onMessageToView', event.type, event.data);\n this.view?.dispatchMessage(event.uuid, event.type, event.data, event.traverseChildren);\n }\n\n #onProxyFailed(reason: unknown) {\n // destroy() freezes this instance and destroys its signals; a proxy may report its failure afterwards\n if (this.#isDestroyed) return;\n\n this.logger.error('the environment proxy failed', reason);\n\n // the failure ends this proxy's turn the same way a reassignment would: a start of its own\n // that resolves afterwards must not report a lost environment as ready\n ++this.#proxyGeneration;\n\n try {\n // the reason before the consequence: ContextLost follows from dropping proxyReady.\n // `emitStrict()` so that one listener that cannot cope does not keep the rest from hearing\n // that the environment is gone; what it threw travels on to the proxy that reported the\n // failure, which is where this method has always put it.\n emitStrict(this as ShadowEnv, ShadowEnv.ProxyFailed, reason, this as ShadowEnv);\n } finally {\n // in the `finally`, because losing the environment is not up for debate:\n // a listener that throws must not leave `isReady` claiming otherwise\n this.proxyReady = false;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["signal"]
|
|
7
7
|
}
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { ComponentContext } from './ComponentContext.js';
|
|
2
|
+
interface ViewComponentOptions {
|
|
3
|
+
parent?: ViewComponent | undefined;
|
|
4
|
+
order?: number | undefined;
|
|
5
|
+
context?: ComponentContext | undefined;
|
|
6
|
+
uuid?: string | undefined;
|
|
7
|
+
autoDestructionOnParentRemoval?: boolean | undefined;
|
|
8
|
+
}
|
|
2
9
|
export declare class ViewComponent {
|
|
3
10
|
#private;
|
|
11
|
+
/**
|
|
12
|
+
* The last thing a component says before {@link ViewComponent#destroy} takes every subscription
|
|
13
|
+
* off it. An integration that put something on the instance — event subscriptions, an own
|
|
14
|
+
* `dispatchEvent` — hears it here and can set that up again on the same component; a context
|
|
15
|
+
* that takes the component back in (`vc.context = ctx`) revives it under the same uuid.
|
|
16
|
+
*
|
|
17
|
+
* It goes out directly on the component rather than through {@link ViewComponent#dispatchEvent},
|
|
18
|
+
* so an own `dispatchEvent` an integration installed does not see it and cannot carry it further.
|
|
19
|
+
* Leaving a context says nothing: `vc.context = null` detaches the component and keeps everything
|
|
20
|
+
* on it.
|
|
21
|
+
*
|
|
22
|
+
* A listener that throws is reported through `console.warn` and costs neither the listeners
|
|
23
|
+
* behind it their turn nor the component the rest of its teardown.
|
|
24
|
+
*
|
|
25
|
+
* Not to be confused with the `Destroyed` exported at the top level of the package: that one is
|
|
26
|
+
* `'destroyed'` and belongs to the worker channel, this one is `'view-component-destroyed'`.
|
|
27
|
+
*/
|
|
28
|
+
static readonly Destroyed = "view-component-destroyed";
|
|
4
29
|
get uuid(): string;
|
|
5
30
|
get token(): string | undefined;
|
|
6
31
|
set token(token: string | undefined);
|
|
@@ -8,19 +33,31 @@ export declare class ViewComponent {
|
|
|
8
33
|
set parent(parent: ViewComponent | null | undefined);
|
|
9
34
|
get context(): ComponentContext | undefined;
|
|
10
35
|
/**
|
|
11
|
-
* @throws {ComponentContextDisposedError} if the new
|
|
12
|
-
* keeps its current
|
|
13
|
-
* one can actually be joined.
|
|
36
|
+
* @throws {ComponentContextDisposedError} if the new {@link ComponentContext} has been disposed.
|
|
37
|
+
* The component keeps its current one in that case: leaving the old one is only worth it if
|
|
38
|
+
* the new one can actually be joined.
|
|
39
|
+
* @throws {ComponentUuidInUseError} if another component of the new {@link ComponentContext}
|
|
40
|
+
* holds this component's uuid. A uuid names one component of a {@link ComponentContext} at a
|
|
41
|
+
* time, and it is free again once its holder has left. This one is thrown after the old
|
|
42
|
+
* {@link ComponentContext} has been left, so the component holds none afterwards — assign one
|
|
43
|
+
* again to take it back in.
|
|
14
44
|
*/
|
|
15
45
|
set context(context: ComponentContext | null | undefined);
|
|
16
46
|
/**
|
|
17
47
|
* A destroyed component is detached from its {@link ComponentContext}: it no longer appears
|
|
18
48
|
* in any change trail and no longer has a corresponding entity.
|
|
19
49
|
*
|
|
20
|
-
* Every mutation that only concerns the component itself (`token`, `order`, properties,
|
|
21
|
-
* `removeFromParent
|
|
22
|
-
*
|
|
23
|
-
*
|
|
50
|
+
* Every mutation that only concerns the component itself (`token`, `order`, properties,
|
|
51
|
+
* `removeFromParent`) is silently ignored while destroyed. Operations that would tie a second,
|
|
52
|
+
* live component to it (`addChild`, the `parent` setter) throw instead, because ignoring them
|
|
53
|
+
* would leave the caller with a wrong picture of the entity tree.
|
|
54
|
+
* {@link ViewComponent#destroy} finds nothing left to detach and still takes off whatever lies
|
|
55
|
+
* on the component at that moment.
|
|
56
|
+
*
|
|
57
|
+
* Assigning `null` or `undefined` to {@link ViewComponent#context} reports the same state: it
|
|
58
|
+
* detaches the component without silencing it, so {@link ViewComponent#dispatchEvent} reaches
|
|
59
|
+
* every listener on it. After a {@link ViewComponent#destroy} it reaches those registered since.
|
|
60
|
+
* Children are not traversed either way.
|
|
24
61
|
*
|
|
25
62
|
* Assigning a {@link ViewComponent#context} revives the component under the same uuid.
|
|
26
63
|
*/
|
|
@@ -40,13 +77,18 @@ export declare class ViewComponent {
|
|
|
40
77
|
* parent is destroyed, so they remain reachable.
|
|
41
78
|
*/
|
|
42
79
|
get autoDestructionOnParentRemoval(): boolean;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Create a component: `new ViewComponent(token, parent)` hangs it under another component, and
|
|
82
|
+
* `new ViewComponent(token, options)` takes `parent`, `order`, `context`, `uuid` and
|
|
83
|
+
* `autoDestructionOnParentRemoval` as an object.
|
|
84
|
+
*
|
|
85
|
+
* A {@link ViewComponent} given in place of the options object is read as the parent and as
|
|
86
|
+
* nothing else — `uuid`, `order` and `context` come from their usual sources, never from it.
|
|
87
|
+
*
|
|
88
|
+
* A component created without a `token` carries {@link VoidToken} (`'#void'`).
|
|
89
|
+
*/
|
|
90
|
+
constructor(token?: string, parent?: ViewComponent);
|
|
91
|
+
constructor(token?: string, options?: ViewComponentOptions);
|
|
50
92
|
isChildOf(parent: ViewComponent): boolean;
|
|
51
93
|
removeFromParent(): void;
|
|
52
94
|
addChild(child: ViewComponent): void;
|
|
@@ -55,9 +97,31 @@ export declare class ViewComponent {
|
|
|
55
97
|
* A destroyed component always returns `false`.
|
|
56
98
|
*/
|
|
57
99
|
setProperty<T = unknown>(name: string, value: T, isEqual?: (a: T, b: T) => boolean): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Mark a property as set without giving it a value. The entity holds the key and reads it as
|
|
102
|
+
* `undefined`; {@link ViewComponent.setProperty} with `undefined` removes it instead.
|
|
103
|
+
*
|
|
104
|
+
* @returns `true` if this differs from the last value written to the change trail.
|
|
105
|
+
* A destroyed component always returns `false`.
|
|
106
|
+
*/
|
|
107
|
+
setPropertyWithoutValue(name: string): boolean;
|
|
58
108
|
removeProperty(name: string): void;
|
|
59
109
|
dispatchShadowObjectsEvent(type: string, data: unknown, transferables?: Transferable[]): void;
|
|
60
110
|
dispatchEvent(type: string, data: unknown, traverseChildren: boolean): void;
|
|
111
|
+
/**
|
|
112
|
+
* End the component: it leaves its {@link ComponentContext} and goes silent. Every `on()` and
|
|
113
|
+
* `once()` subscription made on it is removed, and a `dispatchEvent` an integration installed on
|
|
114
|
+
* the instance is dropped with them. A promise from `onceAsync()` is not reached and settles
|
|
115
|
+
* only when the event it waits for arrives.
|
|
116
|
+
*
|
|
117
|
+
* Calling it more than once is safe. Each call takes off what is on the component at that
|
|
118
|
+
* moment — a subscription made afterwards is heard again.
|
|
119
|
+
*
|
|
120
|
+
* {@link ViewComponent.Destroyed} goes out on the component right before the subscriptions come
|
|
121
|
+
* off, so whoever holds something on it hears it. A listener that wants its subscription back
|
|
122
|
+
* does not set it up inside the handler — that one would come off with the rest.
|
|
123
|
+
*/
|
|
61
124
|
destroy(): void;
|
|
62
125
|
}
|
|
126
|
+
export {};
|
|
63
127
|
//# sourceMappingURL=ViewComponent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewComponent.d.ts","sourceRoot":"","sources":["../../../src/view/ViewComponent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ViewComponent.d.ts","sourceRoot":"","sources":["../../../src/view/ViewComponent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC;AA4BtF,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACvC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,8BAA8B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtD;AAED,qBAAa,aAAa;;IACxB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,QAAQ,CAAC,SAAS,8BAA8B;IAYvD,IAAI,IAAI,WAEP;IAED,IAAI,KAAK,IAIQ,MAAM,GAAG,SAAS,CAFlC;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAKlC;IAED,IAAI,MAAM,IAAI,aAAa,GAAG,SAAS,CAEtC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EAMlD;IAED,IAAI,OAAO,IAAI,gBAAgB,GAAG,SAAS,CAE1C;IAED;;;;;;;;;OASG;IACH,IAAI,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAqCvD;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;OAIG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAMzC;IAED;;;;;;OAMG;IACH,IAAI,8BAA8B,IAAI,OAAO,CAE5C;IAOD;;;;;;;;;OASG;IACH,YAAY,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,EAAE;IACpD,YAAY,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,EAAE;IAwB5D,SAAS,CAAC,MAAM,EAAE,aAAa,WAE9B;IAED,gBAAgB,SAOf;IAED,QAAQ,CAAC,KAAK,EAAE,aAAa,QAqB5B;IAED;;;OAGG;IACH,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,GAAG,OAAO,CAE3F;IAED;;;;;;OAMG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7C;IAED,cAAc,CAAC,IAAI,EAAE,MAAM,QAE1B;IAED,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,YAAY,EAAE,QAErF;IAED,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,QAcnE;IAiBD;;;;;;;;;;;;OAYG;IACH,OAAO,SAiBN;CACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emitSafe, eventize, off } from "@spearwolf/eventize";
|
|
2
2
|
import { VoidToken } from "../constants.js";
|
|
3
3
|
import { generateUUID } from "../utils/generateUUID.js";
|
|
4
4
|
import { ComponentContext, ComponentContextDisposedError } from "./ComponentContext.js";
|
|
@@ -15,8 +15,31 @@ function assertUsableAsParent(parent, childContext) {
|
|
|
15
15
|
if (parent.context !== childContext) {
|
|
16
16
|
throw new ViewComponentError("cannot add a child from another context");
|
|
17
17
|
}
|
|
18
|
+
if (!childContext.hasComponent(parent)) {
|
|
19
|
+
throw new ViewComponentError("cannot add a child to a view component its context does not hold");
|
|
20
|
+
}
|
|
18
21
|
}
|
|
19
22
|
class ViewComponent {
|
|
23
|
+
static {
|
|
24
|
+
/**
|
|
25
|
+
* The last thing a component says before {@link ViewComponent#destroy} takes every subscription
|
|
26
|
+
* off it. An integration that put something on the instance — event subscriptions, an own
|
|
27
|
+
* `dispatchEvent` — hears it here and can set that up again on the same component; a context
|
|
28
|
+
* that takes the component back in (`vc.context = ctx`) revives it under the same uuid.
|
|
29
|
+
*
|
|
30
|
+
* It goes out directly on the component rather than through {@link ViewComponent#dispatchEvent},
|
|
31
|
+
* so an own `dispatchEvent` an integration installed does not see it and cannot carry it further.
|
|
32
|
+
* Leaving a context says nothing: `vc.context = null` detaches the component and keeps everything
|
|
33
|
+
* on it.
|
|
34
|
+
*
|
|
35
|
+
* A listener that throws is reported through `console.warn` and costs neither the listeners
|
|
36
|
+
* behind it their turn nor the component the rest of its teardown.
|
|
37
|
+
*
|
|
38
|
+
* Not to be confused with the `Destroyed` exported at the top level of the package: that one is
|
|
39
|
+
* `'destroyed'` and belongs to the worker channel, this one is `'view-component-destroyed'`.
|
|
40
|
+
*/
|
|
41
|
+
this.Destroyed = "view-component-destroyed";
|
|
42
|
+
}
|
|
20
43
|
#uuid;
|
|
21
44
|
#token;
|
|
22
45
|
#context;
|
|
@@ -49,9 +72,14 @@ class ViewComponent {
|
|
|
49
72
|
return this.#context;
|
|
50
73
|
}
|
|
51
74
|
/**
|
|
52
|
-
* @throws {ComponentContextDisposedError} if the new
|
|
53
|
-
* keeps its current
|
|
54
|
-
* one can actually be joined.
|
|
75
|
+
* @throws {ComponentContextDisposedError} if the new {@link ComponentContext} has been disposed.
|
|
76
|
+
* The component keeps its current one in that case: leaving the old one is only worth it if
|
|
77
|
+
* the new one can actually be joined.
|
|
78
|
+
* @throws {ComponentUuidInUseError} if another component of the new {@link ComponentContext}
|
|
79
|
+
* holds this component's uuid. A uuid names one component of a {@link ComponentContext} at a
|
|
80
|
+
* time, and it is free again once its holder has left. This one is thrown after the old
|
|
81
|
+
* {@link ComponentContext} has been left, so the component holds none afterwards — assign one
|
|
82
|
+
* again to take it back in.
|
|
55
83
|
*/
|
|
56
84
|
set context(context) {
|
|
57
85
|
const next = context ?? void 0;
|
|
@@ -61,8 +89,9 @@ class ViewComponent {
|
|
|
61
89
|
`the view component ${this.#uuid} cannot join the component context because it has been disposed`
|
|
62
90
|
);
|
|
63
91
|
}
|
|
92
|
+
const previousContext = this.#context;
|
|
64
93
|
if (this.#context) {
|
|
65
|
-
this
|
|
94
|
+
this.#leaveContext();
|
|
66
95
|
}
|
|
67
96
|
this.#context = next;
|
|
68
97
|
try {
|
|
@@ -71,15 +100,25 @@ class ViewComponent {
|
|
|
71
100
|
this.#context = void 0;
|
|
72
101
|
throw error;
|
|
73
102
|
}
|
|
103
|
+
if (next != null && previousContext != null) {
|
|
104
|
+
previousContext.transferPropertiesTo(this, next);
|
|
105
|
+
}
|
|
74
106
|
}
|
|
75
107
|
/**
|
|
76
108
|
* A destroyed component is detached from its {@link ComponentContext}: it no longer appears
|
|
77
109
|
* in any change trail and no longer has a corresponding entity.
|
|
78
110
|
*
|
|
79
|
-
* Every mutation that only concerns the component itself (`token`, `order`, properties,
|
|
80
|
-
* `removeFromParent
|
|
81
|
-
*
|
|
82
|
-
*
|
|
111
|
+
* Every mutation that only concerns the component itself (`token`, `order`, properties,
|
|
112
|
+
* `removeFromParent`) is silently ignored while destroyed. Operations that would tie a second,
|
|
113
|
+
* live component to it (`addChild`, the `parent` setter) throw instead, because ignoring them
|
|
114
|
+
* would leave the caller with a wrong picture of the entity tree.
|
|
115
|
+
* {@link ViewComponent#destroy} finds nothing left to detach and still takes off whatever lies
|
|
116
|
+
* on the component at that moment.
|
|
117
|
+
*
|
|
118
|
+
* Assigning `null` or `undefined` to {@link ViewComponent#context} reports the same state: it
|
|
119
|
+
* detaches the component without silencing it, so {@link ViewComponent#dispatchEvent} reaches
|
|
120
|
+
* every listener on it. After a {@link ViewComponent#destroy} it reaches those registered since.
|
|
121
|
+
* Children are not traversed either way.
|
|
83
122
|
*
|
|
84
123
|
* Assigning a {@link ViewComponent#context} revives the component under the same uuid.
|
|
85
124
|
*/
|
|
@@ -132,10 +171,10 @@ class ViewComponent {
|
|
|
132
171
|
}
|
|
133
172
|
removeFromParent() {
|
|
134
173
|
if (this.#parent) {
|
|
135
|
-
this.#context?.removeFromParent(this
|
|
174
|
+
this.#context?.removeFromParent(this, this.#parent);
|
|
136
175
|
this.#parent = void 0;
|
|
137
176
|
} else {
|
|
138
|
-
this.#context?.moveToRoot(this
|
|
177
|
+
this.#context?.moveToRoot(this);
|
|
139
178
|
}
|
|
140
179
|
}
|
|
141
180
|
addChild(child) {
|
|
@@ -163,6 +202,16 @@ class ViewComponent {
|
|
|
163
202
|
setProperty(name, value, isEqual) {
|
|
164
203
|
return this.#context?.setProperty(this, name, value, isEqual) ?? false;
|
|
165
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Mark a property as set without giving it a value. The entity holds the key and reads it as
|
|
207
|
+
* `undefined`; {@link ViewComponent.setProperty} with `undefined` removes it instead.
|
|
208
|
+
*
|
|
209
|
+
* @returns `true` if this differs from the last value written to the change trail.
|
|
210
|
+
* A destroyed component always returns `false`.
|
|
211
|
+
*/
|
|
212
|
+
setPropertyWithoutValue(name) {
|
|
213
|
+
return this.#context?.setPropertyWithoutValue(this, name) ?? false;
|
|
214
|
+
}
|
|
166
215
|
removeProperty(name) {
|
|
167
216
|
this.#context?.removeProperty(this, name);
|
|
168
217
|
}
|
|
@@ -170,17 +219,44 @@ class ViewComponent {
|
|
|
170
219
|
this.#context?.dispatchShadowObjectsEvent(this, type, data, transferables);
|
|
171
220
|
}
|
|
172
221
|
dispatchEvent(type, data, traverseChildren) {
|
|
173
|
-
|
|
222
|
+
emitSafe(this, type, data);
|
|
174
223
|
if (traverseChildren) {
|
|
175
224
|
for (const child of this.#context?.getChildren(this) ?? []) {
|
|
176
225
|
child.dispatchEvent(type, data, traverseChildren);
|
|
177
226
|
}
|
|
178
227
|
}
|
|
179
228
|
}
|
|
180
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Leave the context without ending the component. What a consumer put on the instance — event
|
|
231
|
+
* subscriptions, an own `dispatchEvent` — belongs to the component and not to its membership in
|
|
232
|
+
* a context, so a component that is taken back in keeps answering with all of it.
|
|
233
|
+
*/
|
|
234
|
+
#leaveContext() {
|
|
181
235
|
this.removeFromParent();
|
|
182
|
-
this.#context
|
|
236
|
+
const context = this.#context;
|
|
183
237
|
this.#context = void 0;
|
|
238
|
+
context?.destroyComponent(this);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* End the component: it leaves its {@link ComponentContext} and goes silent. Every `on()` and
|
|
242
|
+
* `once()` subscription made on it is removed, and a `dispatchEvent` an integration installed on
|
|
243
|
+
* the instance is dropped with them. A promise from `onceAsync()` is not reached and settles
|
|
244
|
+
* only when the event it waits for arrives.
|
|
245
|
+
*
|
|
246
|
+
* Calling it more than once is safe. Each call takes off what is on the component at that
|
|
247
|
+
* moment — a subscription made afterwards is heard again.
|
|
248
|
+
*
|
|
249
|
+
* {@link ViewComponent.Destroyed} goes out on the component right before the subscriptions come
|
|
250
|
+
* off, so whoever holds something on it hears it. A listener that wants its subscription back
|
|
251
|
+
* does not set it up inside the handler — that one would come off with the rest.
|
|
252
|
+
*/
|
|
253
|
+
destroy() {
|
|
254
|
+
this.#leaveContext();
|
|
255
|
+
if (Object.hasOwn(this, "dispatchEvent")) {
|
|
256
|
+
delete this.dispatchEvent;
|
|
257
|
+
}
|
|
258
|
+
emitSafe(this, ViewComponent.Destroyed);
|
|
259
|
+
off(this);
|
|
184
260
|
}
|
|
185
261
|
}
|
|
186
262
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/view/ViewComponent.ts"],
|
|
4
|
-
"sourcesContent": ["import {emit, eventize} from '@spearwolf/eventize';\nimport {VoidToken} from '../constants.js';\nimport {generateUUID} from '../utils/generateUUID.js';\nimport {ComponentContext, ComponentContextDisposedError} from './ComponentContext.js';\n\nclass ViewComponentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ViewComponentError';\n }\n}\n\n/**\n * Guards every path that attaches a child to a parent: the constructor, {@link ViewComponent#addChild}\n * and the `parent` setter all funnel through here so that they fail with the same message.\n */\nfunction assertUsableAsParent(parent: ViewComponent, childContext: ComponentContext | undefined) {\n if (parent.context == null) {\n throw new ViewComponentError('cannot add a child to a destroyed view component');\n }\n if (parent.context !== childContext) {\n throw new ViewComponentError('cannot add a child from another context');\n }\n}\n\nexport class ViewComponent {\n readonly #uuid: string;\n\n #token: string;\n\n #context?: ComponentContext;\n\n #parent?: ViewComponent;\n #order = 0;\n readonly #autoDestructionOnParentRemoval: boolean;\n\n get uuid() {\n return this.#uuid;\n }\n\n get token() {\n return this.#token;\n }\n\n set token(token: string | undefined) {\n token ??= VoidToken;\n if (token === this.#token) return;\n this.#token = token;\n this.#context?.changeToken(this, token);\n }\n\n get parent(): ViewComponent | undefined {\n return this.#parent;\n }\n\n set parent(parent: ViewComponent | null | undefined) {\n if (parent) {\n parent.addChild(this);\n } else {\n this.removeFromParent();\n }\n }\n\n get context(): ComponentContext | undefined {\n return this.#context;\n }\n\n /**\n * @throws {ComponentContextDisposedError} if the new context has been disposed. The component\n * keeps its current context in that case: leaving the old one is only worth it if the new\n * one can actually be joined.\n */\n set context(context: ComponentContext | null | undefined) {\n const next = context ?? undefined;\n if (this.#context === next) return;\n\n // checked before the teardown below, so a rejected join costs the component nothing\n if (next?.isDisposed) {\n throw new ComponentContextDisposedError(\n `the view component ${this.#uuid} cannot join the component context because it has been disposed`,\n );\n }\n\n if (this.#context) {\n this.destroy();\n }\n\n this.#context = next;\n\n try {\n next?.addComponent(this);\n } catch (error) {\n // a failed join must not leave a destroyed component pointing at a context that never\n // took it in \u2014 every later mutation would silently go nowhere while `isDestroyed` lies\n this.#context = undefined;\n throw error;\n }\n }\n\n /**\n * A destroyed component is detached from its {@link ComponentContext}: it no longer appears\n * in any change trail and no longer has a corresponding entity.\n *\n * Every mutation that only concerns the component itself (`token`, `order`, properties, events,\n * `removeFromParent`, `destroy`) is silently ignored while destroyed. Operations that would tie\n * a second, live component to it (`addChild`, the `parent` setter) throw instead, because\n * ignoring them would leave the caller with a wrong picture of the entity tree.\n *\n * Assigning a {@link ViewComponent#context} revives the component under the same uuid.\n */\n get isDestroyed(): boolean {\n return this.#context == null;\n }\n\n /**\n * The order property sets the order to lay out a component in a children array of the parent component.\n *\n * Items in a children array are sorted by ascending order value and then by their insertion order.\n */\n get order(): number {\n return this.#order;\n }\n\n set order(order: number | null | undefined) {\n const prevOrder = this.#order;\n this.#order = order ?? 0;\n if (prevOrder !== this.#order) {\n this.#context?.changeOrder(this);\n }\n }\n\n /**\n * Whether the corresponding entity should be destroyed when its parent entity is destroyed.\n *\n * Set via the constructor option of the same name; immutable after creation.\n * Children that opt out (the default) are promoted to root entities when their\n * parent is destroyed, so they remain reachable.\n */\n get autoDestructionOnParentRemoval(): boolean {\n return this.#autoDestructionOnParentRemoval;\n }\n\n constructor(\n token: string,\n options?: {\n parent?: ViewComponent;\n order?: number;\n context?: ComponentContext;\n uuid?: string;\n autoDestructionOnParentRemoval?: boolean;\n },\n ) {\n eventize(this);\n\n if (options instanceof ViewComponent) {\n options = {parent: options};\n }\n\n this.#uuid = options?.uuid ?? generateUUID();\n\n this.#token = token ?? VoidToken;\n this.#order = options?.order ?? 0;\n this.#parent = options?.parent;\n this.#autoDestructionOnParentRemoval = options?.autoDestructionOnParentRemoval ?? false;\n\n const ctx = options?.context ?? ComponentContext.get();\n\n if (this.#parent) {\n assertUsableAsParent(this.#parent, ctx);\n }\n\n this.context = ctx;\n }\n\n isChildOf(parent: ViewComponent) {\n return this.#parent === parent;\n }\n\n removeFromParent() {\n if (this.#parent) {\n this.#context?.removeFromParent(this.uuid, this.#parent);\n this.#parent = undefined;\n } else {\n this.#context?.moveToRoot(this.uuid);\n }\n }\n\n addChild(child: ViewComponent) {\n if (child.#context == null) {\n throw new ViewComponentError('cannot add a destroyed view component as a child');\n }\n\n assertUsableAsParent(this, child.#context);\n\n // walking up via #parent is safe: the entity tree is kept acyclic by exactly this guard\n for (let ancestor: ViewComponent | undefined = this; ancestor != null; ancestor = ancestor.#parent) {\n if (ancestor === child) {\n throw new ViewComponentError(\n 'cannot add the component itself or one of its ancestors as a child: this would create a cycle',\n );\n }\n }\n\n if (!child.isChildOf(this)) {\n child.removeFromParent();\n child.#parent = this;\n this.#context!.addToChildren(this, child);\n }\n }\n\n /**\n * @returns `true` if the value differs from the last one written to the change trail.\n * A destroyed component always returns `false`.\n */\n setProperty<T = unknown>(name: string, value: T, isEqual?: (a: T, b: T) => boolean): boolean {\n return this.#context?.setProperty(this, name, value, isEqual) ?? false;\n }\n\n removeProperty(name: string) {\n this.#context?.removeProperty(this, name);\n }\n\n dispatchShadowObjectsEvent(type: string, data: unknown, transferables?: Transferable[]) {\n this.#context?.dispatchShadowObjectsEvent(this, type, data, transferables);\n }\n\n dispatchEvent(type: string, data: unknown, traverseChildren: boolean) {\n emit(this as ViewComponent, type, data);\n\n if (traverseChildren) {\n for (const child of this.#context?.getChildren(this) ?? []) {\n child.dispatchEvent(type, data, traverseChildren);\n }\n }\n }\n\n destroy() {\n this.removeFromParent();\n this.#context?.destroyComponent(this);\n this.#context = undefined;\n }\n}"],
|
|
5
|
-
"mappings": "AAAA,SAAQ,
|
|
4
|
+
"sourcesContent": ["import {emitSafe, eventize, off} from '@spearwolf/eventize';\nimport {VoidToken} from '../constants.js';\nimport {generateUUID} from '../utils/generateUUID.js';\nimport {ComponentContext, ComponentContextDisposedError} from './ComponentContext.js';\n\nclass ViewComponentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ViewComponentError';\n }\n}\n\n/**\n * Guards every path that attaches a child to a parent: the constructor, {@link ViewComponent#addChild}\n * and the `parent` setter all funnel through here so that they fail with the same message.\n */\nfunction assertUsableAsParent(parent: ViewComponent, childContext: ComponentContext | undefined) {\n if (parent.context == null) {\n throw new ViewComponentError('cannot add a child to a destroyed view component');\n }\n if (parent.context !== childContext) {\n throw new ViewComponentError('cannot add a child from another context');\n }\n // the two checks above ask the instance which context it names; this one asks the context\n // whether it holds the instance. Both have to agree before a child is hung on the parent \u2014 a\n // parent the context does not hold would take the child into a children list nobody reads\n if (!childContext.hasComponent(parent)) {\n throw new ViewComponentError('cannot add a child to a view component its context does not hold');\n }\n}\n\ninterface ViewComponentOptions {\n parent?: ViewComponent | undefined;\n order?: number | undefined;\n context?: ComponentContext | undefined;\n uuid?: string | undefined;\n autoDestructionOnParentRemoval?: boolean | undefined;\n}\n\nexport class ViewComponent {\n /**\n * The last thing a component says before {@link ViewComponent#destroy} takes every subscription\n * off it. An integration that put something on the instance \u2014 event subscriptions, an own\n * `dispatchEvent` \u2014 hears it here and can set that up again on the same component; a context\n * that takes the component back in (`vc.context = ctx`) revives it under the same uuid.\n *\n * It goes out directly on the component rather than through {@link ViewComponent#dispatchEvent},\n * so an own `dispatchEvent` an integration installed does not see it and cannot carry it further.\n * Leaving a context says nothing: `vc.context = null` detaches the component and keeps everything\n * on it.\n *\n * A listener that throws is reported through `console.warn` and costs neither the listeners\n * behind it their turn nor the component the rest of its teardown.\n *\n * Not to be confused with the `Destroyed` exported at the top level of the package: that one is\n * `'destroyed'` and belongs to the worker channel, this one is `'view-component-destroyed'`.\n */\n static readonly Destroyed = 'view-component-destroyed';\n\n readonly #uuid: string;\n\n #token: string;\n\n #context?: ComponentContext | undefined;\n\n #parent?: ViewComponent | undefined;\n #order = 0;\n readonly #autoDestructionOnParentRemoval: boolean;\n\n get uuid() {\n return this.#uuid;\n }\n\n get token() {\n return this.#token;\n }\n\n set token(token: string | undefined) {\n token ??= VoidToken;\n if (token === this.#token) return;\n this.#token = token;\n this.#context?.changeToken(this, token);\n }\n\n get parent(): ViewComponent | undefined {\n return this.#parent;\n }\n\n set parent(parent: ViewComponent | null | undefined) {\n if (parent) {\n parent.addChild(this);\n } else {\n this.removeFromParent();\n }\n }\n\n get context(): ComponentContext | undefined {\n return this.#context;\n }\n\n /**\n * @throws {ComponentContextDisposedError} if the new {@link ComponentContext} has been disposed.\n * The component keeps its current one in that case: leaving the old one is only worth it if\n * the new one can actually be joined.\n * @throws {ComponentUuidInUseError} if another component of the new {@link ComponentContext}\n * holds this component's uuid. A uuid names one component of a {@link ComponentContext} at a\n * time, and it is free again once its holder has left. This one is thrown after the old\n * {@link ComponentContext} has been left, so the component holds none afterwards \u2014 assign one\n * again to take it back in.\n */\n set context(context: ComponentContext | null | undefined) {\n const next = context ?? undefined;\n if (this.#context === next) return;\n\n // checked before the teardown below, so a rejected join costs the component nothing\n if (next?.isDisposed) {\n throw new ComponentContextDisposedError(\n `the view component ${this.#uuid} cannot join the component context because it has been disposed`,\n );\n }\n\n const previousContext = this.#context;\n\n if (this.#context) {\n // leaving, not ending: an element that leaves the document hands its context back and takes\n // the same component in again when it returns, so the subscriptions on it have to survive\n this.#leaveContext();\n }\n\n this.#context = next;\n\n try {\n next?.addComponent(this);\n } catch (error) {\n // a failed join must not leave a destroyed component pointing at a context that never\n // took it in \u2014 every later mutation would silently go nowhere while `isDestroyed` lies\n this.#context = undefined;\n throw error;\n }\n\n // after the join, not before it: `addComponent` is what creates the ComponentChanges the\n // properties are written into, so they end up in the same CreateEntities change as the token\n // and the parent rather than in a separate one behind it. A move to no context carries\n // nothing \u2014 there is no receiver for it\n if (next != null && previousContext != null) {\n previousContext.transferPropertiesTo(this, next);\n }\n }\n\n /**\n * A destroyed component is detached from its {@link ComponentContext}: it no longer appears\n * in any change trail and no longer has a corresponding entity.\n *\n * Every mutation that only concerns the component itself (`token`, `order`, properties,\n * `removeFromParent`) is silently ignored while destroyed. Operations that would tie a second,\n * live component to it (`addChild`, the `parent` setter) throw instead, because ignoring them\n * would leave the caller with a wrong picture of the entity tree.\n * {@link ViewComponent#destroy} finds nothing left to detach and still takes off whatever lies\n * on the component at that moment.\n *\n * Assigning `null` or `undefined` to {@link ViewComponent#context} reports the same state: it\n * detaches the component without silencing it, so {@link ViewComponent#dispatchEvent} reaches\n * every listener on it. After a {@link ViewComponent#destroy} it reaches those registered since.\n * Children are not traversed either way.\n *\n * Assigning a {@link ViewComponent#context} revives the component under the same uuid.\n */\n get isDestroyed(): boolean {\n return this.#context == null;\n }\n\n /**\n * The order property sets the order to lay out a component in a children array of the parent component.\n *\n * Items in a children array are sorted by ascending order value and then by their insertion order.\n */\n get order(): number {\n return this.#order;\n }\n\n set order(order: number | null | undefined) {\n const prevOrder = this.#order;\n this.#order = order ?? 0;\n if (prevOrder !== this.#order) {\n this.#context?.changeOrder(this);\n }\n }\n\n /**\n * Whether the corresponding entity should be destroyed when its parent entity is destroyed.\n *\n * Set via the constructor option of the same name; immutable after creation.\n * Children that opt out (the default) are promoted to root entities when their\n * parent is destroyed, so they remain reachable.\n */\n get autoDestructionOnParentRemoval(): boolean {\n return this.#autoDestructionOnParentRemoval;\n }\n\n // the parent form is declared first on purpose: ViewComponent has a member for every optional\n // member of the options type, so without it the compiler resolves `new ViewComponent(t, parent)`\n // structurally against the options object and reads `uuid`, `order` and `context` off the parent\n // \u2014 a meaning the body does not have. An object literal is assignable to neither ViewComponent\n // (private fields) nor the first overload, and lands on the second one\n /**\n * Create a component: `new ViewComponent(token, parent)` hangs it under another component, and\n * `new ViewComponent(token, options)` takes `parent`, `order`, `context`, `uuid` and\n * `autoDestructionOnParentRemoval` as an object.\n *\n * A {@link ViewComponent} given in place of the options object is read as the parent and as\n * nothing else \u2014 `uuid`, `order` and `context` come from their usual sources, never from it.\n *\n * A component created without a `token` carries {@link VoidToken} (`'#void'`).\n */\n constructor(token?: string, parent?: ViewComponent);\n constructor(token?: string, options?: ViewComponentOptions);\n constructor(token?: string, options?: ViewComponent | ViewComponentOptions) {\n eventize(this);\n\n if (options instanceof ViewComponent) {\n options = {parent: options};\n }\n\n this.#uuid = options?.uuid ?? generateUUID();\n\n this.#token = token ?? VoidToken;\n this.#order = options?.order ?? 0;\n this.#parent = options?.parent;\n this.#autoDestructionOnParentRemoval = options?.autoDestructionOnParentRemoval ?? false;\n\n const ctx = options?.context ?? ComponentContext.get();\n\n if (this.#parent) {\n assertUsableAsParent(this.#parent, ctx);\n }\n\n this.context = ctx;\n }\n\n isChildOf(parent: ViewComponent) {\n return this.#parent === parent;\n }\n\n removeFromParent() {\n if (this.#parent) {\n this.#context?.removeFromParent(this, this.#parent);\n this.#parent = undefined;\n } else {\n this.#context?.moveToRoot(this);\n }\n }\n\n addChild(child: ViewComponent) {\n if (child.#context == null) {\n throw new ViewComponentError('cannot add a destroyed view component as a child');\n }\n\n assertUsableAsParent(this, child.#context);\n\n // walking up via #parent is safe: the entity tree is kept acyclic by exactly this guard\n for (let ancestor: ViewComponent | undefined = this; ancestor != null; ancestor = ancestor.#parent) {\n if (ancestor === child) {\n throw new ViewComponentError(\n 'cannot add the component itself or one of its ancestors as a child: this would create a cycle',\n );\n }\n }\n\n if (!child.isChildOf(this)) {\n child.removeFromParent();\n child.#parent = this;\n this.#context!.addToChildren(this, child);\n }\n }\n\n /**\n * @returns `true` if the value differs from the last one written to the change trail.\n * A destroyed component always returns `false`.\n */\n setProperty<T = unknown>(name: string, value: T, isEqual?: (a: T, b: T) => boolean): boolean {\n return this.#context?.setProperty(this, name, value, isEqual) ?? false;\n }\n\n /**\n * Mark a property as set without giving it a value. The entity holds the key and reads it as\n * `undefined`; {@link ViewComponent.setProperty} with `undefined` removes it instead.\n *\n * @returns `true` if this differs from the last value written to the change trail.\n * A destroyed component always returns `false`.\n */\n setPropertyWithoutValue(name: string): boolean {\n return this.#context?.setPropertyWithoutValue(this, name) ?? false;\n }\n\n removeProperty(name: string) {\n this.#context?.removeProperty(this, name);\n }\n\n dispatchShadowObjectsEvent(type: string, data: unknown, transferables?: Transferable[]) {\n this.#context?.dispatchShadowObjectsEvent(this, type, data, transferables);\n }\n\n dispatchEvent(type: string, data: unknown, traverseChildren: boolean) {\n // `emitSafe()`: this is the delivery path of everything the shadow environment sends to the\n // view, and it is a fan-out twice over -- over the listeners of one component, and, with\n // `traverseChildren`, over a whole subtree. Under the plain dispatch one listener that throws\n // would end the delivery and cut off every component below it, and the error would leave\n // through the message channel that has no caller left to catch it. A failure is reported\n // through `console.warn` and costs only itself.\n emitSafe(this as ViewComponent, type, data);\n\n if (traverseChildren) {\n for (const child of this.#context?.getChildren(this) ?? []) {\n child.dispatchEvent(type, data, traverseChildren);\n }\n }\n }\n\n /**\n * Leave the context without ending the component. What a consumer put on the instance \u2014 event\n * subscriptions, an own `dispatchEvent` \u2014 belongs to the component and not to its membership in\n * a context, so a component that is taken back in keeps answering with all of it.\n */\n #leaveContext() {\n this.removeFromParent();\n\n // the context pointer goes first: destroyComponent() detaches every component that still\n // names the context, and would call destroy() on it otherwise\n const context = this.#context;\n this.#context = undefined;\n context?.destroyComponent(this);\n }\n\n /**\n * End the component: it leaves its {@link ComponentContext} and goes silent. Every `on()` and\n * `once()` subscription made on it is removed, and a `dispatchEvent` an integration installed on\n * the instance is dropped with them. A promise from `onceAsync()` is not reached and settles\n * only when the event it waits for arrives.\n *\n * Calling it more than once is safe. Each call takes off what is on the component at that\n * moment \u2014 a subscription made afterwards is heard again.\n *\n * {@link ViewComponent.Destroyed} goes out on the component right before the subscriptions come\n * off, so whoever holds something on it hears it. A listener that wants its subscription back\n * does not set it up inside the handler \u2014 that one would come off with the rest.\n */\n destroy() {\n this.#leaveContext();\n\n // an integration may shadow `dispatchEvent` on the instance; dropping the own property\n // uncovers the method on the prototype again, it does not remove it\n if (Object.hasOwn(this, 'dispatchEvent')) {\n delete (this as {dispatchEvent?: ViewComponent['dispatchEvent']}).dispatchEvent;\n }\n\n // last, and before the subscriptions come off: the component is in its final shape by now, and\n // whoever hears this still hears it. Guarded, because the line below is the point of the whole\n // method: under the plain dispatch a listener that throws would leave through `destroy()` and\n // take the `off()` with it, and the component would end its life holding every subscription it\n // was supposed to be rid of.\n emitSafe(this as ViewComponent, ViewComponent.Destroyed);\n\n off(this);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/view/cloneChangeTrail.ts"],
|
|
4
|
-
"sourcesContent": ["import type {ChangeTrailType} from '../types.js';\n\nexport function cloneChangeTrail(trails: ChangeTrailType): ChangeTrailType {\n return trails.map((trail) => {\n if (trail.transferables && trail.transferables.length > 0) {\n const {transferables, ...data} = trail;\n return structuredClone(data, {transfer: transferables
|
|
5
|
-
"mappings": "AAEO,SAAS,iBAAiB,QAA0C;AACzE,SAAO,OAAO,IAAI,CAAC,UAAU;AAC3B,QAAI,MAAM,iBAAiB,MAAM,cAAc,SAAS,GAAG;AACzD,YAAM,EAAC,eAAe,GAAG,KAAI,IAAI;AACjC,aAAO,gBAAgB,MAAM,EAAC,UAAU,
|
|
4
|
+
"sourcesContent": ["import type {ChangeTrailType} from '../types.js';\n\nexport function cloneChangeTrail(trails: ChangeTrailType): ChangeTrailType {\n return trails.map((trail) => {\n if (trail.transferables && trail.transferables.length > 0) {\n const {transferables, ...data} = trail;\n return structuredClone(data, {transfer: transferables});\n } else {\n return structuredClone(trail);\n }\n });\n}\n"],
|
|
5
|
+
"mappings": "AAEO,SAAS,iBAAiB,QAA0C;AACzE,SAAO,OAAO,IAAI,CAAC,UAAU;AAC3B,QAAI,MAAM,iBAAiB,MAAM,cAAc,SAAS,GAAG;AACzD,YAAM,EAAC,eAAe,GAAG,KAAI,IAAI;AACjC,aAAO,gBAAgB,MAAM,EAAC,UAAU,cAAa,CAAC;AAAA,IACxD,OAAO;AACL,aAAO,gBAAgB,KAAK;AAAA,IAC9B;AAAA,EACF,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { Kernel, type MessageToViewEvent } from '../in-the-dark/Kernel.js';
|
|
2
|
+
import { ConsoleLogger } from '../utils/ConsoleLogger.js';
|
|
3
|
+
/**
|
|
4
|
+
* A payload this side can read is an object: every branch below takes a `type` off it and
|
|
5
|
+
* then reads further fields. `null`, `undefined`, a number or a string come from someone who
|
|
6
|
+
* does not speak this protocol, and reading through them takes the whole worker down over one
|
|
7
|
+
* message. Deliberately a plain boolean rather than a type predicate: `event.data` is `any` on
|
|
8
|
+
* both call sites, and narrowing it would only cost the branches below the payload types they
|
|
9
|
+
* already have.
|
|
10
|
+
*/
|
|
11
|
+
export declare const isReadableMessageData: (data: unknown) => boolean;
|
|
2
12
|
export interface MessageRouterOptions {
|
|
3
13
|
kernel?: Kernel;
|
|
4
14
|
postMessage?: typeof self.postMessage;
|
|
5
15
|
}
|
|
6
16
|
export declare class MessageRouter {
|
|
7
17
|
#private;
|
|
18
|
+
/** The logger this router reports through. */
|
|
19
|
+
get logger(): ConsoleLogger;
|
|
20
|
+
/** Whether this router has been torn down. Once it is, every message that reaches it is discarded. */
|
|
21
|
+
get isDestroyed(): boolean;
|
|
8
22
|
kernel: Kernel;
|
|
9
23
|
postMessage: typeof self.postMessage;
|
|
10
24
|
constructor(options?: MessageRouterOptions);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageRouter.d.ts","sourceRoot":"","sources":["../../../src/worker/MessageRouter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageRouter.d.ts","sourceRoot":"","sources":["../../../src/worker/MessageRouter.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,MAAM,EAAE,KAAK,kBAAkB,EAAC,MAAM,0BAA0B,CAAC;AAUzE,OAAO,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AAgBxD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,SAAU,OAAO,KAAG,OAAoD,CAAC;AA2B3G,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,IAAI,CAAC,WAAW,CAAC;CACvC;AAED,qBAAa,aAAa;;IAcxB,8CAA8C;IAC9C,IAAI,MAAM,IAAI,aAAa,CAE1B;IAID,sGAAsG;IACtG,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,MAAM,EAAE,MAAM,CAAC;IAEf,WAAW,EAAE,OAAO,IAAI,CAAC,WAAW,CAAC;IAErC,YAAY,OAAO,CAAC,EAAE,oBAAoB,EAMzC;IAED,KAAK,CAAC,KAAK,EAAE,YAAY,QAqCxB;IAED,eAAe,CAAC,KAAK,EAAE,kBAAkB,QAIxC;CA2GF"}
|