@spearwolf/shadow-objects 0.34.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.
Files changed (174) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +11 -2
  3. package/bundle.js +4 -2
  4. package/package.json +9 -1
  5. package/src/WorkerTimeoutError.d.ts +4 -3
  6. package/src/WorkerTimeoutError.d.ts.map +1 -1
  7. package/src/WorkerTimeoutError.js.map +2 -2
  8. package/src/constants.d.ts +8 -4
  9. package/src/constants.d.ts.map +1 -1
  10. package/src/constants.js +6 -0
  11. package/src/constants.js.map +2 -2
  12. package/src/elements/ShaePropElement.d.ts +15 -6
  13. package/src/elements/ShaePropElement.d.ts.map +1 -1
  14. package/src/elements/ShaePropElement.js +31 -7
  15. package/src/elements/ShaePropElement.js.map +2 -2
  16. package/src/elements/ShaeWorkerElement.d.ts +10 -0
  17. package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
  18. package/src/elements/ShaeWorkerElement.js +69 -2
  19. package/src/elements/ShaeWorkerElement.js.map +2 -2
  20. package/src/elements/constants.d.ts +3 -0
  21. package/src/elements/constants.d.ts.map +1 -1
  22. package/src/elements/constants.js +6 -0
  23. package/src/elements/constants.js.map +2 -2
  24. package/src/in-the-dark/Entity.d.ts +30 -0
  25. package/src/in-the-dark/Entity.d.ts.map +1 -1
  26. package/src/in-the-dark/Entity.js +38 -0
  27. package/src/in-the-dark/Entity.js.map +2 -2
  28. package/src/in-the-dark/Kernel.d.ts +24 -1
  29. package/src/in-the-dark/Kernel.d.ts.map +1 -1
  30. package/src/in-the-dark/Kernel.js +56 -1
  31. package/src/in-the-dark/Kernel.js.map +2 -2
  32. package/src/in-the-dark/Registry.d.ts +7 -1
  33. package/src/in-the-dark/Registry.d.ts.map +1 -1
  34. package/src/in-the-dark/Registry.js +29 -0
  35. package/src/in-the-dark/Registry.js.map +2 -2
  36. package/src/in-the-dark/ShadowObjectCreationScope.d.ts +8 -1
  37. package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -1
  38. package/src/in-the-dark/ShadowObjectCreationScope.js +23 -16
  39. package/src/in-the-dark/ShadowObjectCreationScope.js.map +2 -2
  40. package/src/in-the-dark/SignalsPath.d.ts +2 -0
  41. package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
  42. package/src/in-the-dark/SignalsPath.js +4 -0
  43. package/src/in-the-dark/SignalsPath.js.map +2 -2
  44. package/src/in-the-dark/displayName.d.ts +4 -0
  45. package/src/in-the-dark/displayName.d.ts.map +1 -0
  46. package/src/in-the-dark/displayName.js +5 -0
  47. package/src/in-the-dark/displayName.js.map +7 -0
  48. package/src/index.d.ts +2 -0
  49. package/src/index.d.ts.map +1 -1
  50. package/src/index.js +4 -0
  51. package/src/index.js.map +2 -2
  52. package/src/inspect/createKernelSnapshot.d.ts +17 -0
  53. package/src/inspect/createKernelSnapshot.d.ts.map +1 -0
  54. package/src/inspect/createKernelSnapshot.js +247 -0
  55. package/src/inspect/createKernelSnapshot.js.map +7 -0
  56. package/src/inspect/createViewSnapshot.d.ts +10 -0
  57. package/src/inspect/createViewSnapshot.d.ts.map +1 -0
  58. package/src/inspect/createViewSnapshot.js +143 -0
  59. package/src/inspect/createViewSnapshot.js.map +7 -0
  60. package/src/inspect/normalizeInspectRequest.d.ts +32 -0
  61. package/src/inspect/normalizeInspectRequest.d.ts.map +1 -0
  62. package/src/inspect/normalizeInspectRequest.js +55 -0
  63. package/src/inspect/normalizeInspectRequest.js.map +7 -0
  64. package/src/inspect/serializeValue.d.ts +13 -0
  65. package/src/inspect/serializeValue.d.ts.map +1 -0
  66. package/src/inspect/serializeValue.js +149 -0
  67. package/src/inspect/serializeValue.js.map +7 -0
  68. package/src/inspect/types.d.ts +260 -0
  69. package/src/inspect/types.d.ts.map +1 -0
  70. package/src/inspect/types.js +1 -0
  71. package/src/inspect/types.js.map +7 -0
  72. package/src/model-context/ModelContextLike.d.ts +59 -0
  73. package/src/model-context/ModelContextLike.d.ts.map +1 -0
  74. package/src/model-context/ModelContextLike.js +15 -0
  75. package/src/model-context/ModelContextLike.js.map +7 -0
  76. package/src/model-context/exposeShadowEnvsToModelContext.d.ts +48 -0
  77. package/src/model-context/exposeShadowEnvsToModelContext.d.ts.map +1 -0
  78. package/src/model-context/exposeShadowEnvsToModelContext.js +39 -0
  79. package/src/model-context/exposeShadowEnvsToModelContext.js.map +7 -0
  80. package/src/model-context/redactProps.d.ts +14 -0
  81. package/src/model-context/redactProps.d.ts.map +1 -0
  82. package/src/model-context/redactProps.js +22 -0
  83. package/src/model-context/redactProps.js.map +7 -0
  84. package/src/model-context/sharedExposure.d.ts +32 -0
  85. package/src/model-context/sharedExposure.d.ts.map +1 -0
  86. package/src/model-context/sharedExposure.js +108 -0
  87. package/src/model-context/sharedExposure.js.map +7 -0
  88. package/src/model-context/toolSupport.d.ts +72 -0
  89. package/src/model-context/toolSupport.d.ts.map +1 -0
  90. package/src/model-context/toolSupport.js +145 -0
  91. package/src/model-context/toolSupport.js.map +7 -0
  92. package/src/model-context/tools/findEntities.d.ts +15 -0
  93. package/src/model-context/tools/findEntities.d.ts.map +1 -0
  94. package/src/model-context/tools/findEntities.js +71 -0
  95. package/src/model-context/tools/findEntities.js.map +7 -0
  96. package/src/model-context/tools/getEntity.d.ts +16 -0
  97. package/src/model-context/tools/getEntity.d.ts.map +1 -0
  98. package/src/model-context/tools/getEntity.js +65 -0
  99. package/src/model-context/tools/getEntity.js.map +7 -0
  100. package/src/model-context/tools/getEntityTree.d.ts +4 -0
  101. package/src/model-context/tools/getEntityTree.d.ts.map +1 -0
  102. package/src/model-context/tools/getEntityTree.js +58 -0
  103. package/src/model-context/tools/getEntityTree.js.map +7 -0
  104. package/src/model-context/tools/getRegistry.d.ts +14 -0
  105. package/src/model-context/tools/getRegistry.d.ts.map +1 -0
  106. package/src/model-context/tools/getRegistry.js +39 -0
  107. package/src/model-context/tools/getRegistry.js.map +7 -0
  108. package/src/model-context/tools/index.d.ts +9 -0
  109. package/src/model-context/tools/index.d.ts.map +1 -0
  110. package/src/model-context/tools/index.js +16 -0
  111. package/src/model-context/tools/index.js.map +7 -0
  112. package/src/model-context/tools/listEnvs.d.ts +33 -0
  113. package/src/model-context/tools/listEnvs.d.ts.map +1 -0
  114. package/src/model-context/tools/listEnvs.js +37 -0
  115. package/src/model-context/tools/listEnvs.js.map +7 -0
  116. package/src/model-context.d.ts +13 -0
  117. package/src/model-context.d.ts.map +1 -0
  118. package/src/model-context.js +15 -0
  119. package/src/model-context.js.map +7 -0
  120. package/src/shadow-objects.d.ts +2 -0
  121. package/src/shadow-objects.d.ts.map +1 -1
  122. package/src/shadow-objects.js +4 -0
  123. package/src/shadow-objects.js.map +2 -2
  124. package/src/testing/TestEntity.d.ts +79 -0
  125. package/src/testing/TestEntity.d.ts.map +1 -0
  126. package/src/testing/TestEntity.js +135 -0
  127. package/src/testing/TestEntity.js.map +7 -0
  128. package/src/testing/createTestKernel.d.ts +11 -0
  129. package/src/testing/createTestKernel.d.ts.map +1 -0
  130. package/src/testing/createTestKernel.js +132 -0
  131. package/src/testing/createTestKernel.js.map +7 -0
  132. package/src/testing/mountShadowObject.d.ts +21 -0
  133. package/src/testing/mountShadowObject.d.ts.map +1 -0
  134. package/src/testing/mountShadowObject.js +72 -0
  135. package/src/testing/mountShadowObject.js.map +7 -0
  136. package/src/testing/recordKernelErrors.d.ts +21 -0
  137. package/src/testing/recordKernelErrors.d.ts.map +1 -0
  138. package/src/testing/recordKernelErrors.js +39 -0
  139. package/src/testing/recordKernelErrors.js.map +7 -0
  140. package/src/testing/settle.d.ts +15 -0
  141. package/src/testing/settle.d.ts.map +1 -0
  142. package/src/testing/settle.js +19 -0
  143. package/src/testing/settle.js.map +7 -0
  144. package/src/testing/types.d.ts +119 -0
  145. package/src/testing/types.d.ts.map +1 -0
  146. package/src/testing/types.js +1 -0
  147. package/src/testing/types.js.map +7 -0
  148. package/src/testing.d.ts +14 -0
  149. package/src/testing.d.ts.map +1 -0
  150. package/src/testing.js +11 -0
  151. package/src/testing.js.map +7 -0
  152. package/src/types.d.ts +150 -10
  153. package/src/types.d.ts.map +1 -1
  154. package/src/utils/attr-utils.d.ts +5 -0
  155. package/src/utils/attr-utils.d.ts.map +1 -1
  156. package/src/utils/attr-utils.js +2 -0
  157. package/src/utils/attr-utils.js.map +2 -2
  158. package/src/view/IShadowObjectEnvProxy.d.ts +9 -0
  159. package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
  160. package/src/view/LocalShadowObjectEnv.d.ts +7 -0
  161. package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
  162. package/src/view/LocalShadowObjectEnv.js +15 -1
  163. package/src/view/LocalShadowObjectEnv.js.map +2 -2
  164. package/src/view/RemoteWorkerEnv.d.ts +22 -3
  165. package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
  166. package/src/view/RemoteWorkerEnv.js +50 -1
  167. package/src/view/RemoteWorkerEnv.js.map +2 -2
  168. package/src/view/ShadowEnv.d.ts +30 -0
  169. package/src/view/ShadowEnv.d.ts.map +1 -1
  170. package/src/view/ShadowEnv.js +101 -0
  171. package/src/view/ShadowEnv.js.map +3 -3
  172. package/src/worker/MessageRouter.d.ts.map +1 -1
  173. package/src/worker/MessageRouter.js +23 -0
  174. package/src/worker/MessageRouter.js.map +2 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/view/ShadowEnv.ts"],
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 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 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\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 #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 * 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": []
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 +1 @@
1
- {"version":3,"file":"MessageRouter.d.ts","sourceRoot":"","sources":["../../../src/worker/MessageRouter.ts"],"names":[],"mappings":"AAaA,OAAO,EAAC,MAAM,EAAE,KAAK,kBAAkB,EAAC,MAAM,0BAA0B,CAAC;AAEzE,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,QAiCxB;IAED,eAAe,CAAC,KAAK,EAAE,kBAAkB,QAIxC;CAyFF"}
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"}
@@ -7,11 +7,14 @@ import {
7
7
  Destroy,
8
8
  Destroyed,
9
9
  ImportedModule,
10
+ Inspect,
11
+ Inspected,
10
12
  MessageToView,
11
13
  ShadowObjectsExport
12
14
  } from "../constants.js";
13
15
  import { importModule, missingShadowObjectsExportMessage } from "../in-the-dark/importModule.js";
14
16
  import { Kernel } from "../in-the-dark/Kernel.js";
17
+ import { createKernelSnapshot } from "../inspect/createKernelSnapshot.js";
15
18
  import { ConsoleLogger } from "../utils/ConsoleLogger.js";
16
19
  import { toUrlString } from "../utils/toUrlString.js";
17
20
  const isReadableMessageData = (data) => typeof data === "object" && data !== null;
@@ -65,6 +68,9 @@ class MessageRouter {
65
68
  case ChangeTrail:
66
69
  this.#onChangeTrail(data);
67
70
  break;
71
+ case Inspect:
72
+ this.#onInspect(data);
73
+ break;
68
74
  case Destroy:
69
75
  this.#onDestroy(data);
70
76
  break;
@@ -125,6 +131,23 @@ class MessageRouter {
125
131
  this.postMessage({ type: AppliedChangeTrail, serial: data.serial });
126
132
  }
127
133
  }
134
+ /**
135
+ * An inspection runs through the same queue as the change trails, so the snapshot reflects every
136
+ * trail routed before the request and none routed after it -- the one ordering guarantee
137
+ * `ShadowEnv.inspect()` documents. It changes nothing and exists only for its answer, so unlike a
138
+ * change trail it is answered whether or not the serial names a waiter; and a builder that throws
139
+ * is answered too, with the two fields of the throw that survive the wire -- without a reply the
140
+ * caller would sit out its `inspectTimeout` and learn nothing about why.
141
+ */
142
+ #onInspect(data) {
143
+ try {
144
+ const snapshot = createKernelSnapshot(this.kernel, data.request);
145
+ this.postMessage({ type: Inspected, serial: data.serial, snapshot });
146
+ } catch (error) {
147
+ this.logger.error("failed to inspect the kernel", error);
148
+ this.postMessage({ type: Inspected, serial: data.serial, ...describeError(error) });
149
+ }
150
+ }
128
151
  #onDestroy(data) {
129
152
  this.logger.debug("on destroy", data);
130
153
  this.#isDestroyed = true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/worker/MessageRouter.ts"],
4
- "sourcesContent": ["import {off, on} from '@spearwolf/eventize';\nimport {ChangeTrailRefusedError} from '../ChangeTrailRefusedError.js';\nimport {\n AppliedChangeTrail,\n ChangeTrail,\n Configure,\n Destroy,\n Destroyed,\n ImportedModule,\n MessageToView,\n ShadowObjectsExport,\n} from '../constants.js';\nimport {importModule, missingShadowObjectsExportMessage} from '../in-the-dark/importModule.js';\nimport {Kernel, type MessageToViewEvent} from '../in-the-dark/Kernel.js';\nimport type {AppliedChangeTrailEvent, ImportedModuleEvent, ShadowObjectsModule, SyncEvent} from '../types.js';\nimport {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {toUrlString} from '../utils/toUrlString.js';\n\ninterface ConfigurePayloadData {\n importModule?: string;\n}\n\n/**\n * The teardown request carries nothing but its own type: there is no field a sender could vary,\n * and the router reads none. Named all the same -- the three routes then read alike, and a\n * payload that says which message it is says more than one that says nothing at all.\n */\ninterface DestroyPayloadData {\n type: typeof Destroy;\n}\n\n/**\n * A payload this side can read is an object: every branch below takes a `type` off it and\n * then reads further fields. `null`, `undefined`, a number or a string come from someone who\n * does not speak this protocol, and reading through them takes the whole worker down over one\n * message. Deliberately a plain boolean rather than a type predicate: `event.data` is `any` on\n * both call sites, and narrowing it would only cost the branches below the payload types they\n * already have.\n */\nexport const isReadableMessageData = (data: unknown): boolean => typeof data === 'object' && data !== null;\n\n/**\n * Reduces a throw to the two fields that survive the wire. `RemoteWorkerEnv` builds an error\n * from them, and it decides between a confirmation and a refusal by whether `error` is there\n * at all -- so the wording must never come out empty, not even for an `Error` carrying no\n * message of its own.\n *\n * Reading a throw means running its code: `String()` goes through its `toString()`, and\n * `message` and `name` can be getters of the thrown value's own making. A value that throws\n * from there -- an object whose `toString()` fails, one with no prototype at all -- must not\n * take the answer with it: the caller in the view is waiting on a reply, and without one it\n * sits out its `configureTimeout` or `changeTrailTimeout` and learns nothing about why.\n */\nconst describeError = (error: unknown): {error: string; errorName?: string} => {\n try {\n return error instanceof Error\n ? {error: error.message || String(error), errorName: error.name}\n : {error: String(error) || 'unknown error'};\n } catch {\n // No name goes with it: whatever the value would have said about itself is exactly what\n // could not be read. The throw is already on the console -- both callers log it before\n // they ask for a description.\n return {error: 'an error that cannot be described'};\n }\n};\n\nexport interface MessageRouterOptions {\n kernel?: Kernel;\n postMessage?: typeof self.postMessage;\n}\n\nexport class MessageRouter {\n #importedModules: Set<ShadowObjectsModule> = new Set();\n\n /**\n * Built in the field initializer rather than on first read, the way the lazy getter\n * `WorkerRuntime.logger` has to be: a `MessageRouter` is only ever built from\n * `WorkerRuntime.onmessage`, and only past the branch that answers the `CONSOLE_LOGGER`\n * configuration message and returns -- so by the time this initializer runs, that configuration\n * has already been installed. The `Kernel` this router holds builds its own logger the same way,\n * in its own field initializer, on the same guarantee; which of the two is built first is not\n * something either one depends on.\n */\n readonly #logger = new ConsoleLogger('MessageRouter');\n\n /** The logger this router reports through. */\n get logger(): ConsoleLogger {\n return this.#logger;\n }\n\n #isDestroyed = false;\n\n /** Whether this router has been torn down. Once it is, every message that reaches it is discarded. */\n get isDestroyed(): boolean {\n return this.#isDestroyed;\n }\n\n kernel: Kernel;\n\n postMessage: typeof self.postMessage;\n\n constructor(options?: MessageRouterOptions) {\n this.kernel = options?.kernel ?? new Kernel();\n\n this.postMessage = options?.postMessage ?? self.postMessage.bind(self);\n\n on(this.kernel, MessageToView, 'onMessageToView', this);\n }\n\n route(event: MessageEvent) {\n const data = event.data;\n\n if (!isReadableMessageData(data)) {\n this.logger.debug('discarding a message it cannot read', data);\n return;\n }\n\n // After the teardown the kernel is empty and nothing of it reaches the view any more, so a\n // change trail applied here would build entities nobody ever hears about. A second destroy\n // meets the same barrier: the confirmation belongs to the destroy that was answered, and the\n // one waiter there is settled on it.\n if (this.#isDestroyed) {\n this.logger.debug('discarding a message that arrived after the teardown', data.type);\n return;\n }\n\n switch (data.type) {\n case Configure:\n this.#configure(data);\n break;\n\n case ChangeTrail:\n this.#onChangeTrail(data);\n break;\n\n case Destroy:\n this.#onDestroy(data);\n break;\n\n default:\n this.logger.warn('unknown message', data.type ?? data);\n }\n }\n\n onMessageToView(event: MessageToViewEvent) {\n const {transferables: transfer, ...data} = event;\n // WebIDL defaults `transfer` to `[]`, so a missing key and an empty list are the same call.\n this.postMessage({type: MessageToView, data}, {transfer: transfer ?? []});\n }\n\n async #configure(data: ConfigurePayloadData) {\n const url = data.importModule;\n try {\n if (!url) throw new Error('missing \"importModule\" url');\n const module = await import(/* @vite-ignore */ toUrlString(url));\n\n // the import outlived a teardown that happened while it was in flight: registering it now\n // would fill a kernel that is already down\n if (this.#isDestroyed) {\n this.logger.debug('discarding a module that arrived after the teardown', url);\n return;\n }\n\n if (module[ShadowObjectsExport]) {\n await importModule(this.kernel, module[ShadowObjectsExport], this.#importedModules);\n this.postMessage({type: ImportedModule, url});\n } else {\n this.postMessage({\n type: ImportedModule,\n url,\n error: missingShadowObjectsExportMessage,\n } as ImportedModuleEvent);\n }\n } catch (error) {\n // an error report is not gated on a debug switch, the same way `RemoteWorkerEnv` keeps its\n // own error reports ungated: a failure is worth printing regardless of what debug logging\n // is set to\n this.logger.error('failed to import module', error);\n this.postMessage({type: ImportedModule, url, ...describeError(error)} as ImportedModuleEvent);\n }\n }\n\n #onChangeTrail(data: SyncEvent) {\n // One change trail, one confirmation -- and only where a serial asked for one. A caller\n // waiting on that serial decides between rejection and resolution on the first message it\n // sees, so a second one behind it would make the outcome a matter of order.\n // A serial is either on the message or it is not; zero is a number like any other, and\n // the sender that chose it is waiting for its answer.\n try {\n this.kernel.run(data);\n } catch (error) {\n this.logger.error('failed to apply change trail', error);\n if (data.serial != null) {\n const refusal = error instanceof ChangeTrailRefusedError ? error : undefined;\n this.postMessage({\n type: AppliedChangeTrail,\n serial: data.serial,\n // what the entry threw, not the refusal wrapped around it: the number travels in a\n // field of its own, so the reason stays the reason\n ...describeError(refusal?.cause ?? error),\n ...(refusal ? {appliedCount: refusal.appliedCount} : {}),\n } as AppliedChangeTrailEvent);\n }\n return;\n }\n\n if (data.serial != null) {\n this.postMessage({type: AppliedChangeTrail, serial: data.serial} as AppliedChangeTrailEvent);\n }\n }\n\n #onDestroy(data: DestroyPayloadData) {\n this.logger.debug('on destroy', data);\n\n // `route()` is the barrier, so this runs once: every later message, a second destroy included,\n // is discarded before it gets here.\n this.#isDestroyed = true;\n\n // taken off before the kernel goes down: a message a teardown callback dispatches reaches\n // the view a microtask later, behind the confirmation this call is about to send. What an\n // `onDestroy` sends to the view during a worker teardown is therefore dropped, where a\n // local environment still delivers it.\n off(this.kernel, this);\n\n try {\n this.kernel.destroy();\n } catch (error) {\n // the confirmation is owed either way -- without it the view sits out its destroy\n // timeout before terminating the worker, and learns nothing it could act on\n this.logger.error('failed to tear the kernel down', error);\n }\n\n // releases the module objects this router imported; nothing can import into it again\n this.#importedModules.clear();\n\n this.postMessage({type: Destroyed});\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {off, on} from '@spearwolf/eventize';\nimport {ChangeTrailRefusedError} from '../ChangeTrailRefusedError.js';\nimport {\n AppliedChangeTrail,\n ChangeTrail,\n Configure,\n Destroy,\n Destroyed,\n ImportedModule,\n Inspect,\n Inspected,\n MessageToView,\n ShadowObjectsExport,\n} from '../constants.js';\nimport {importModule, missingShadowObjectsExportMessage} from '../in-the-dark/importModule.js';\nimport {Kernel, type MessageToViewEvent} from '../in-the-dark/Kernel.js';\nimport {createKernelSnapshot} from '../inspect/createKernelSnapshot.js';\nimport type {\n AppliedChangeTrailEvent,\n ImportedModuleEvent,\n InspectEvent,\n InspectedEvent,\n ShadowObjectsModule,\n SyncEvent,\n} from '../types.js';\nimport {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {toUrlString} from '../utils/toUrlString.js';\n\ninterface ConfigurePayloadData {\n importModule?: string;\n}\n\n/**\n * The teardown request carries nothing but its own type: there is no field a sender could vary,\n * and the router reads none. Named all the same -- the four routes then read alike, and a\n * payload that says which message it is says more than one that says nothing at all.\n */\ninterface DestroyPayloadData {\n type: typeof Destroy;\n}\n\n/**\n * A payload this side can read is an object: every branch below takes a `type` off it and\n * then reads further fields. `null`, `undefined`, a number or a string come from someone who\n * does not speak this protocol, and reading through them takes the whole worker down over one\n * message. Deliberately a plain boolean rather than a type predicate: `event.data` is `any` on\n * both call sites, and narrowing it would only cost the branches below the payload types they\n * already have.\n */\nexport const isReadableMessageData = (data: unknown): boolean => typeof data === 'object' && data !== null;\n\n/**\n * Reduces a throw to the two fields that survive the wire. `RemoteWorkerEnv` builds an error\n * from them, and it decides between a confirmation and a refusal by whether `error` is there\n * at all -- so the wording must never come out empty, not even for an `Error` carrying no\n * message of its own.\n *\n * Reading a throw means running its code: `String()` goes through its `toString()`, and\n * `message` and `name` can be getters of the thrown value's own making. A value that throws\n * from there -- an object whose `toString()` fails, one with no prototype at all -- must not\n * take the answer with it: the caller in the view is waiting on a reply, and without one it\n * sits out its `configureTimeout`, `changeTrailTimeout` or `inspectTimeout` and learns nothing about why.\n */\nconst describeError = (error: unknown): {error: string; errorName?: string} => {\n try {\n return error instanceof Error\n ? {error: error.message || String(error), errorName: error.name}\n : {error: String(error) || 'unknown error'};\n } catch {\n // No name goes with it: whatever the value would have said about itself is exactly what\n // could not be read. The throw is already on the console -- every caller logs it before\n // it asks for a description.\n return {error: 'an error that cannot be described'};\n }\n};\n\nexport interface MessageRouterOptions {\n kernel?: Kernel;\n postMessage?: typeof self.postMessage;\n}\n\nexport class MessageRouter {\n #importedModules: Set<ShadowObjectsModule> = new Set();\n\n /**\n * Built in the field initializer rather than on first read, the way the lazy getter\n * `WorkerRuntime.logger` has to be: a `MessageRouter` is only ever built from\n * `WorkerRuntime.onmessage`, and only past the branch that answers the `CONSOLE_LOGGER`\n * configuration message and returns -- so by the time this initializer runs, that configuration\n * has already been installed. The `Kernel` this router holds builds its own logger the same way,\n * in its own field initializer, on the same guarantee; which of the two is built first is not\n * something either one depends on.\n */\n readonly #logger = new ConsoleLogger('MessageRouter');\n\n /** The logger this router reports through. */\n get logger(): ConsoleLogger {\n return this.#logger;\n }\n\n #isDestroyed = false;\n\n /** Whether this router has been torn down. Once it is, every message that reaches it is discarded. */\n get isDestroyed(): boolean {\n return this.#isDestroyed;\n }\n\n kernel: Kernel;\n\n postMessage: typeof self.postMessage;\n\n constructor(options?: MessageRouterOptions) {\n this.kernel = options?.kernel ?? new Kernel();\n\n this.postMessage = options?.postMessage ?? self.postMessage.bind(self);\n\n on(this.kernel, MessageToView, 'onMessageToView', this);\n }\n\n route(event: MessageEvent) {\n const data = event.data;\n\n if (!isReadableMessageData(data)) {\n this.logger.debug('discarding a message it cannot read', data);\n return;\n }\n\n // After the teardown the kernel is empty and nothing of it reaches the view any more, so a\n // change trail applied here would build entities nobody ever hears about. A second destroy\n // meets the same barrier: the confirmation belongs to the destroy that was answered, and the\n // one waiter there is settled on it.\n if (this.#isDestroyed) {\n this.logger.debug('discarding a message that arrived after the teardown', data.type);\n return;\n }\n\n switch (data.type) {\n case Configure:\n this.#configure(data);\n break;\n\n case ChangeTrail:\n this.#onChangeTrail(data);\n break;\n\n case Inspect:\n this.#onInspect(data);\n break;\n\n case Destroy:\n this.#onDestroy(data);\n break;\n\n default:\n this.logger.warn('unknown message', data.type ?? data);\n }\n }\n\n onMessageToView(event: MessageToViewEvent) {\n const {transferables: transfer, ...data} = event;\n // WebIDL defaults `transfer` to `[]`, so a missing key and an empty list are the same call.\n this.postMessage({type: MessageToView, data}, {transfer: transfer ?? []});\n }\n\n async #configure(data: ConfigurePayloadData) {\n const url = data.importModule;\n try {\n if (!url) throw new Error('missing \"importModule\" url');\n const module = await import(/* @vite-ignore */ toUrlString(url));\n\n // the import outlived a teardown that happened while it was in flight: registering it now\n // would fill a kernel that is already down\n if (this.#isDestroyed) {\n this.logger.debug('discarding a module that arrived after the teardown', url);\n return;\n }\n\n if (module[ShadowObjectsExport]) {\n await importModule(this.kernel, module[ShadowObjectsExport], this.#importedModules);\n this.postMessage({type: ImportedModule, url});\n } else {\n this.postMessage({\n type: ImportedModule,\n url,\n error: missingShadowObjectsExportMessage,\n } as ImportedModuleEvent);\n }\n } catch (error) {\n // an error report is not gated on a debug switch, the same way `RemoteWorkerEnv` keeps its\n // own error reports ungated: a failure is worth printing regardless of what debug logging\n // is set to\n this.logger.error('failed to import module', error);\n this.postMessage({type: ImportedModule, url, ...describeError(error)} as ImportedModuleEvent);\n }\n }\n\n #onChangeTrail(data: SyncEvent) {\n // One change trail, one confirmation -- and only where a serial asked for one. A caller\n // waiting on that serial decides between rejection and resolution on the first message it\n // sees, so a second one behind it would make the outcome a matter of order.\n // A serial is either on the message or it is not; zero is a number like any other, and\n // the sender that chose it is waiting for its answer.\n try {\n this.kernel.run(data);\n } catch (error) {\n this.logger.error('failed to apply change trail', error);\n if (data.serial != null) {\n const refusal = error instanceof ChangeTrailRefusedError ? error : undefined;\n this.postMessage({\n type: AppliedChangeTrail,\n serial: data.serial,\n // what the entry threw, not the refusal wrapped around it: the number travels in a\n // field of its own, so the reason stays the reason\n ...describeError(refusal?.cause ?? error),\n ...(refusal ? {appliedCount: refusal.appliedCount} : {}),\n } as AppliedChangeTrailEvent);\n }\n return;\n }\n\n if (data.serial != null) {\n this.postMessage({type: AppliedChangeTrail, serial: data.serial} as AppliedChangeTrailEvent);\n }\n }\n\n /**\n * An inspection runs through the same queue as the change trails, so the snapshot reflects every\n * trail routed before the request and none routed after it -- the one ordering guarantee\n * `ShadowEnv.inspect()` documents. It changes nothing and exists only for its answer, so unlike a\n * change trail it is answered whether or not the serial names a waiter; and a builder that throws\n * is answered too, with the two fields of the throw that survive the wire -- without a reply the\n * caller would sit out its `inspectTimeout` and learn nothing about why.\n */\n #onInspect(data: InspectEvent) {\n try {\n const snapshot = createKernelSnapshot(this.kernel, data.request);\n this.postMessage({type: Inspected, serial: data.serial, snapshot} as InspectedEvent);\n } catch (error) {\n this.logger.error('failed to inspect the kernel', error);\n this.postMessage({type: Inspected, serial: data.serial, ...describeError(error)} as InspectedEvent);\n }\n }\n\n #onDestroy(data: DestroyPayloadData) {\n this.logger.debug('on destroy', data);\n\n // `route()` is the barrier, so this runs once: every later message, a second destroy included,\n // is discarded before it gets here.\n this.#isDestroyed = true;\n\n // taken off before the kernel goes down: a message a teardown callback dispatches reaches\n // the view a microtask later, behind the confirmation this call is about to send. What an\n // `onDestroy` sends to the view during a worker teardown is therefore dropped, where a\n // local environment still delivers it.\n off(this.kernel, this);\n\n try {\n this.kernel.destroy();\n } catch (error) {\n // the confirmation is owed either way -- without it the view sits out its destroy\n // timeout before terminating the worker, and learns nothing it could act on\n this.logger.error('failed to tear the kernel down', error);\n }\n\n // releases the module objects this router imported; nothing can import into it again\n this.#importedModules.clear();\n\n this.postMessage({type: Destroyed});\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }