@carbonenginejs/runtime-resource 0.12.2 → 0.14.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 (238) hide show
  1. package/README.md +21 -0
  2. package/dist/CjsResMan.js +393 -29
  3. package/dist/CjsResMan.js.map +1 -1
  4. package/dist/format/carbonEffect/CjsCarbonEffectReader.js +165 -118
  5. package/dist/format/carbonEffect/CjsCarbonEffectReader.js.map +1 -1
  6. package/dist/format/carbonEffect/buildCarbonEffectContainer.js +182 -0
  7. package/dist/format/carbonEffect/buildCarbonEffectContainer.js.map +1 -0
  8. package/dist/format/carbonEffect/carbonEffectBackendBlock.js +7 -113
  9. package/dist/format/carbonEffect/carbonEffectBackendBlock.js.map +1 -1
  10. package/dist/format/carbonEffect/carbonEffectRecords.js +258 -66
  11. package/dist/format/carbonEffect/carbonEffectRecords.js.map +1 -1
  12. package/dist/format/carbonEffect/carbonEffectResourceTransform.js +197 -0
  13. package/dist/format/carbonEffect/carbonEffectResourceTransform.js.map +1 -0
  14. package/dist/format/effect/effectBodyInventory.js +146 -0
  15. package/dist/format/effect/effectBodyInventory.js.map +1 -0
  16. package/dist/format/effect/effectPermutationGraph.js +13 -13
  17. package/dist/format/effect/effectPermutationGraph.js.map +1 -1
  18. package/dist/format/index.js +4 -4
  19. package/dist/formats/black/CjsBlackFormat.js +19 -1
  20. package/dist/formats/black/CjsBlackFormat.js.map +1 -1
  21. package/dist/formats/bnk/CjsBnkFormat.js +28 -9
  22. package/dist/formats/bnk/CjsBnkFormat.js.map +1 -1
  23. package/dist/formats/bnk/core/busNodes.js +252 -0
  24. package/dist/formats/bnk/core/busNodes.js.map +1 -0
  25. package/dist/formats/bnk/core/effectNodes.js +147 -0
  26. package/dist/formats/bnk/core/effectNodes.js.map +1 -0
  27. package/dist/formats/bnk/core/eventAction.js +135 -24
  28. package/dist/formats/bnk/core/eventAction.js.map +1 -1
  29. package/dist/formats/bnk/core/globalSettings.js +215 -0
  30. package/dist/formats/bnk/core/globalSettings.js.map +1 -0
  31. package/dist/formats/bnk/core/helpers.js +37 -5
  32. package/dist/formats/bnk/core/helpers.js.map +1 -1
  33. package/dist/formats/bnk/core/musicNodes.js +43 -19
  34. package/dist/formats/bnk/core/musicNodes.js.map +1 -1
  35. package/dist/formats/bnk/core/nodeBase.js +28 -7
  36. package/dist/formats/bnk/core/nodeBase.js.map +1 -1
  37. package/dist/formats/dds/core/helpers.js +40 -1
  38. package/dist/formats/dds/core/helpers.js.map +1 -1
  39. package/dist/formats/dxbc/CjsDxbcFormat.js +45 -0
  40. package/dist/formats/dxbc/CjsDxbcFormat.js.map +1 -1
  41. package/dist/formats/dxbc/core/disassemble.js +240 -0
  42. package/dist/formats/dxbc/core/disassemble.js.map +1 -0
  43. package/dist/formats/gltf/CjsGltfFormat.js +1 -39
  44. package/dist/formats/gltf/CjsGltfFormat.js.map +1 -1
  45. package/dist/formats/gltf/core/helpers.js +1 -21
  46. package/dist/formats/gltf/core/helpers.js.map +1 -1
  47. package/dist/formats/hlsl/CjsHlslFormat.js +0 -21
  48. package/dist/formats/hlsl/CjsHlslFormat.js.map +1 -1
  49. package/dist/formats/hlsl/core/carbonDescriptionToRuntime.js +118 -49
  50. package/dist/formats/hlsl/core/carbonDescriptionToRuntime.js.map +1 -1
  51. package/dist/formats/hlsl/core/detailMapFamily.js +128 -0
  52. package/dist/formats/hlsl/core/detailMapFamily.js.map +1 -0
  53. package/dist/formats/hlsl/core/localLightFamily.js +133 -0
  54. package/dist/formats/hlsl/core/localLightFamily.js.map +1 -0
  55. package/dist/formats/hlsl/core/tr2/resources/HlslEffectRes.js +79 -73
  56. package/dist/formats/hlsl/core/tr2/resources/HlslEffectRes.js.map +1 -1
  57. package/dist/formats/hlsl/core/tr2/shader/HlslEffectBindingManifest.js +8 -1
  58. package/dist/formats/hlsl/core/tr2/shader/HlslEffectBindingManifest.js.map +1 -1
  59. package/dist/formats/hlsl/core/tr2/shader/HlslEffectDescription.js +11 -691
  60. package/dist/formats/hlsl/core/tr2/shader/HlslEffectDescription.js.map +1 -1
  61. package/dist/formats/index.js +4 -2
  62. package/dist/formats/index.js.map +1 -1
  63. package/dist/formats/obj/CjsObjFormat.js +1 -22
  64. package/dist/formats/obj/CjsObjFormat.js.map +1 -1
  65. package/dist/formats/obj/core/helpers.js +1 -21
  66. package/dist/formats/obj/core/helpers.js.map +1 -1
  67. package/dist/formats/pickle/CjsPickleFormat.js +214 -0
  68. package/dist/formats/pickle/CjsPickleFormat.js.map +1 -0
  69. package/dist/formats/pickle/core/CjsPickleProtocol0Reader.js +551 -0
  70. package/dist/formats/pickle/core/CjsPickleProtocol0Reader.js.map +1 -0
  71. package/dist/formats/pickle/index.js +2 -0
  72. package/dist/formats/pickle/index.js.map +1 -0
  73. package/dist/formats/red/core/redGraph.js +11 -8
  74. package/dist/formats/red/core/redGraph.js.map +1 -1
  75. package/dist/formats/stl/CjsStlFormat.js +1 -46
  76. package/dist/formats/stl/CjsStlFormat.js.map +1 -1
  77. package/dist/formats/stl/core/helpers.js +1 -21
  78. package/dist/formats/stl/core/helpers.js.map +1 -1
  79. package/dist/formats/webgl/CjsWebglFormat.js +33 -55
  80. package/dist/formats/webgl/CjsWebglFormat.js.map +1 -1
  81. package/dist/formats/webgl/core/buildGlslEffectContainer.js +70 -0
  82. package/dist/formats/webgl/core/buildGlslEffectContainer.js.map +1 -0
  83. package/dist/formats/webgl/core/effectPackage.js +216 -108
  84. package/dist/formats/webgl/core/effectPackage.js.map +1 -1
  85. package/dist/formats/webgl/core/errors.js +1 -1
  86. package/dist/formats/webgl/core/errors.js.map +1 -1
  87. package/dist/formats/webgl/core/glsl/DxbcGlslEmitter.js +166 -24
  88. package/dist/formats/webgl/core/glsl/DxbcGlslEmitter.js.map +1 -1
  89. package/dist/formats/webgl/core/glsl/DxbcGlslOperandFormatter.js +63 -5
  90. package/dist/formats/webgl/core/glsl/DxbcGlslOperandFormatter.js.map +1 -1
  91. package/dist/formats/webgl/core/glsl/packedLightFixups.js +98 -0
  92. package/dist/formats/webgl/core/glsl/packedLightFixups.js.map +1 -0
  93. package/dist/formats/webgl/core/glslBackendBlock.js +552 -0
  94. package/dist/formats/webgl/core/glslBackendBlock.js.map +1 -0
  95. package/dist/formats/webgl/core/glslBackendBodySet.js +243 -0
  96. package/dist/formats/webgl/core/glslBackendBodySet.js.map +1 -0
  97. package/dist/formats/webgl/core/{cewgCompleteness.js → glslEffectCompleteness.js} +12 -8
  98. package/dist/formats/webgl/core/glslEffectCompleteness.js.map +1 -0
  99. package/dist/formats/webgl/core/helpers.js +33 -172
  100. package/dist/formats/webgl/core/helpers.js.map +1 -1
  101. package/dist/formats/webgl/core/inspectGlslEffectContainer.js +122 -0
  102. package/dist/formats/webgl/core/inspectGlslEffectContainer.js.map +1 -0
  103. package/dist/formats/webgl/core/readGlslEffectContainer.js +249 -0
  104. package/dist/formats/webgl/core/readGlslEffectContainer.js.map +1 -0
  105. package/dist/formats/webgpu/CjsWebgpuFormat.js +15 -15
  106. package/dist/formats/webgpu/CjsWebgpuFormat.js.map +1 -1
  107. package/dist/formats/webgpu/core/buildCarbonEffectContainer.js +37 -145
  108. package/dist/formats/webgpu/core/buildCarbonEffectContainer.js.map +1 -1
  109. package/dist/formats/webgpu/core/{cewgpu/CewgpuContainer.js → carbonWebgpu/CarbonWebgpuContainer.js} +13 -27
  110. package/dist/formats/webgpu/core/carbonWebgpu/CarbonWebgpuContainer.js.map +1 -0
  111. package/dist/formats/webgpu/core/{cewgpu → carbonWebgpu}/containerViews.js +1 -1
  112. package/dist/formats/webgpu/core/carbonWebgpu/containerViews.js.map +1 -0
  113. package/dist/formats/webgpu/core/{cewgpu → carbonWebgpu}/validateContainer.js +4 -4
  114. package/dist/formats/webgpu/core/carbonWebgpu/validateContainer.js.map +1 -0
  115. package/dist/formats/webgpu/core/effectBackendBodySet.js +18 -18
  116. package/dist/formats/webgpu/core/effectBackendBodySet.js.map +1 -1
  117. package/dist/formats/webgpu/core/errors.js +1 -1
  118. package/dist/formats/webgpu/core/errors.js.map +1 -1
  119. package/dist/formats/webgpu/core/helpers.js +22 -22
  120. package/dist/formats/webgpu/core/helpers.js.map +1 -1
  121. package/dist/formats/webgpu/core/packageEffect.js +11 -15
  122. package/dist/formats/webgpu/core/packageEffect.js.map +1 -1
  123. package/dist/formats/webgpu/core/packageEffectSelection.js +1 -1
  124. package/dist/formats/webgpu/core/packageEffectSelection.js.map +1 -1
  125. package/dist/formats/webgpu/core/wgsl/buildResourceTransformPlan.js +23 -23
  126. package/dist/formats/webgpu/core/wgsl/buildResourceTransformPlan.js.map +1 -1
  127. package/dist/formats/webgpu/core/wgsl/buildWgslSet.js +6 -6
  128. package/dist/formats/webgpu/core/wgsl/buildWgslSet.js.map +1 -1
  129. package/dist/formats/yaml/CjsYamlFormat.js +3 -3
  130. package/dist/formats/yaml/CjsYamlFormat.js.map +1 -1
  131. package/dist/formats/yaml/core/CjsYamlReader.js +4 -6
  132. package/dist/formats/yaml/core/CjsYamlReader.js.map +1 -1
  133. package/dist/formats/yaml/core/helpers.js +27 -1
  134. package/dist/formats/yaml/core/helpers.js.map +1 -1
  135. package/dist/index.js +2 -0
  136. package/dist/index.js.map +1 -1
  137. package/dist/resource/CjsLoadingObject.js +19 -0
  138. package/dist/resource/CjsLoadingObject.js.map +1 -0
  139. package/dist/resource/CjsResource.js +4 -0
  140. package/dist/resource/CjsResource.js.map +1 -1
  141. package/dist/resource/ResourceHandlerMode.js +14 -0
  142. package/dist/resource/ResourceHandlerMode.js.map +1 -0
  143. package/dist/resource/index.js +2 -0
  144. package/dist/resource/index.js.map +1 -1
  145. package/dist/resource/shader/Tr2EffectRes.js +118 -213
  146. package/dist/resource/shader/Tr2EffectRes.js.map +1 -1
  147. package/dist/resource/shader/Tr2Shader.js +37 -15
  148. package/dist/resource/shader/Tr2Shader.js.map +1 -1
  149. package/dist/resource/shader/reflection/Tr2EffectConstant.js +37 -29
  150. package/dist/resource/shader/reflection/Tr2EffectConstant.js.map +1 -1
  151. package/dist/resource/shader/reflection/Tr2EffectDescription.js +39 -34
  152. package/dist/resource/shader/reflection/Tr2EffectDescription.js.map +1 -1
  153. package/dist/resource/shader/reflection/Tr2EffectLibrary.js +51 -30
  154. package/dist/resource/shader/reflection/Tr2EffectLibrary.js.map +1 -1
  155. package/dist/resource/shader/reflection/Tr2EffectParameterAnnotation.js +45 -25
  156. package/dist/resource/shader/reflection/Tr2EffectParameterAnnotation.js.map +1 -1
  157. package/dist/resource/shader/reflection/Tr2EffectResource.js +51 -17
  158. package/dist/resource/shader/reflection/Tr2EffectResource.js.map +1 -1
  159. package/dist/resource/shader/reflection/Tr2EffectStageInput.js +219 -88
  160. package/dist/resource/shader/reflection/Tr2EffectStageInput.js.map +1 -1
  161. package/dist/resource/shader/reflection/Tr2EffectTechnique.js +28 -18
  162. package/dist/resource/shader/reflection/Tr2EffectTechnique.js.map +1 -1
  163. package/dist/resource/shader/reflection/Tr2Pass.js +96 -39
  164. package/dist/resource/shader/reflection/Tr2Pass.js.map +1 -1
  165. package/dist/resource/shader/reflection/carbonRecordFields.js +159 -0
  166. package/dist/resource/shader/reflection/carbonRecordFields.js.map +1 -0
  167. package/dist/resource/shader/reflection/shaderStage.js +22 -0
  168. package/dist/resource/shader/reflection/shaderStage.js.map +1 -0
  169. package/dist/resource/shader/sampler/Tr2SamplerSetup.js +68 -10
  170. package/dist/resource/shader/sampler/Tr2SamplerSetup.js.map +1 -1
  171. package/dist/worker/CjsResManMainThreadLoader.js +9 -7
  172. package/dist/worker/CjsResManMainThreadLoader.js.map +1 -1
  173. package/dist/worker/CjsResManWorker.js +5 -4
  174. package/dist/worker/CjsResManWorker.js.map +1 -1
  175. package/dist/worker/CjsResManWorkerLoader.js +9 -6
  176. package/dist/worker/CjsResManWorkerLoader.js.map +1 -1
  177. package/docs/README.md +2 -2
  178. package/docs/architecture.md +12 -3
  179. package/docs/concepts/resource-lifecycle.md +9 -8
  180. package/docs/concepts/shader-resource-model.md +14 -17
  181. package/docs/concepts/writing-an-engine-adapter.md +1 -1
  182. package/docs/formats/README.md +4 -2
  183. package/docs/formats/carbon-effect-container.md +127 -26
  184. package/docs/formats/dxbc/architecture.md +1 -1
  185. package/docs/formats/dxbc/reference/api.md +28 -0
  186. package/docs/formats/hlsl/README.md +3 -3
  187. package/docs/formats/hlsl/architecture.md +8 -9
  188. package/docs/formats/hlsl/guides/hydrating-json-output.md +3 -5
  189. package/docs/formats/hlsl/guides/reading-effects.md +5 -1
  190. package/docs/formats/hlsl/reference/advanced-analysis.md +20 -25
  191. package/docs/formats/hlsl/reference/api.md +0 -7
  192. package/docs/formats/hlsl/reference/json-graph.md +1 -4
  193. package/docs/formats/pickle.md +82 -0
  194. package/docs/formats/webgl/README.md +71 -13
  195. package/docs/formats/webgl/architecture.md +16 -17
  196. package/docs/formats/webgl/carbon-constant-layouts.md +3 -3
  197. package/docs/formats/webgl/decl-io.md +3 -3
  198. package/docs/formats/webgl/memory-structured.md +27 -8
  199. package/docs/formats/webgl/texture-sample.md +1 -1
  200. package/docs/formats/webgpu/README.md +3 -3
  201. package/docs/formats/webgpu/architecture.md +5 -6
  202. package/docs/formats/webgpu/formats/{cewgpu.md → carbon-webgpu.md} +10 -11
  203. package/docs/formats/webgpu/guides/effect-packaging.md +6 -8
  204. package/docs/formats/webgpu/reference/api.md +11 -12
  205. package/docs/formats/webgpu/reference/wgsl-compatibility.md +11 -8
  206. package/docs/formats/wwise.md +63 -2
  207. package/docs/reference/classes/core.md +10 -0
  208. package/docs/reference/classes/formats.md +40 -48
  209. package/docs/reference/classes/resources.md +2 -2
  210. package/docs/reference/queues.md +88 -4
  211. package/docs/reference/workers.md +7 -0
  212. package/package.json +61 -61
  213. package/dist/format/carbonEffect/carbonDescriptionFromPortable.js +0 -372
  214. package/dist/format/carbonEffect/carbonDescriptionFromPortable.js.map +0 -1
  215. package/dist/format/effect/effectReflectionPackage.js +0 -636
  216. package/dist/format/effect/effectReflectionPackage.js.map +0 -1
  217. package/dist/formats/hlsl/core/HlslReader.js +0 -16
  218. package/dist/formats/hlsl/core/HlslReader.js.map +0 -1
  219. package/dist/formats/hlsl/core/portableReflection.js +0 -848
  220. package/dist/formats/hlsl/core/portableReflection.js.map +0 -1
  221. package/dist/formats/hlsl/portable.js +0 -2
  222. package/dist/formats/hlsl/portable.js.map +0 -1
  223. package/dist/formats/webgl/core/cewg/CewgPackage.js +0 -340
  224. package/dist/formats/webgl/core/cewg/CewgPackage.js.map +0 -1
  225. package/dist/formats/webgl/core/cewg/CewgPackageBuilder.js +0 -104
  226. package/dist/formats/webgl/core/cewg/CewgPackageBuilder.js.map +0 -1
  227. package/dist/formats/webgl/core/cewg/binary.js +0 -19
  228. package/dist/formats/webgl/core/cewg/binary.js.map +0 -1
  229. package/dist/formats/webgl/core/cewgCompleteness.js.map +0 -1
  230. package/dist/formats/webgl/core/effectPackageValidation.js +0 -331
  231. package/dist/formats/webgl/core/effectPackageValidation.js.map +0 -1
  232. package/dist/formats/webgpu/core/cewgpu/CewgpuContainer.js.map +0 -1
  233. package/dist/formats/webgpu/core/cewgpu/containerViews.js.map +0 -1
  234. package/dist/formats/webgpu/core/cewgpu/validateContainer.js.map +0 -1
  235. package/dist/resource/shader/portable.js +0 -33
  236. package/dist/resource/shader/portable.js.map +0 -1
  237. package/docs/formats/hlsl/reference/portable-reflection.md +0 -141
  238. package/docs/formats/webgl/effect-reflection.md +0 -127
package/README.md CHANGED
@@ -40,6 +40,27 @@ const resource = resMan.GetResource("res:/video/intro.mp4");
40
40
  const video = await resource.Ready();
41
41
  ```
42
42
 
43
+ Extensions can also choose the resource/object handler and an ordered reader
44
+ route explicitly. This is useful for Carbon object streams, where either file
45
+ suffix may contain Black bytes or Red/YAML text:
46
+
47
+ ```js
48
+ import { CjsLoadingObject } from "@carbonenginejs/runtime-resource";
49
+ import { CjsBlackFormat } from "@carbonenginejs/runtime-resource/formats/black";
50
+ import { CjsRedFormat } from "@carbonenginejs/runtime-resource/formats/red";
51
+
52
+ resMan.RegisterExtension("red", CjsLoadingObject, [
53
+ CjsBlackFormat,
54
+ CjsRedFormat
55
+ ]);
56
+ resMan.RegisterExtension("black", CjsLoadingObject, [
57
+ CjsBlackFormat,
58
+ CjsRedFormat
59
+ ]);
60
+
61
+ const object = await resMan.Fetch("res:/definition/example.red");
62
+ ```
63
+
43
64
  Browser consumers use worker-backed fetch and declared worker-safe format
44
65
  readers by default, with deterministic main-thread fallback; see
45
66
  [browser worker execution](docs/reference/workers.md).
package/dist/CjsResMan.js CHANGED
@@ -3,6 +3,7 @@ import { CjsEventEmitter } from '@carbonenginejs/runtime-utils/model';
3
3
  import { hasOwnThen } from '@carbonenginejs/runtime-utils/object';
4
4
  import { normalizeResourcePath, normalizePath, normalizeResourceExtension, getResourceExtension } from '@carbonenginejs/runtime-utils/path';
5
5
  import { CjsResource } from './resource/CjsResource.js';
6
+ import { ResourceHandlerMode } from './resource/ResourceHandlerMode.js';
6
7
  import { CjsResManWorkQueue, CjsResManQueue } from './CjsResManWorkQueue.js';
7
8
  import { CjsResManMainThreadLoader } from './worker/CjsResManMainThreadLoader.js';
8
9
  import { CjsResManWorkerLoader } from './worker/CjsResManWorkerLoader.js';
@@ -33,6 +34,10 @@ let nextLocalValueIdentity = 1;
33
34
  * @property {CjsResMan} resMan Owning manager.
34
35
  * @property {object|Function} source Selected source implementation.
35
36
  * @property {string} path Normalized Carbon-style source path.
37
+ * @property {string} resFilePath Normalized lowercase Carbon-style source path.
38
+ * @property {string} ext Normalized lowercase resource extension.
39
+ * @property {string} fileName Normalized lowercase final path component.
40
+ * @property {string|null} url Exact URL passed to a URL-backed source, otherwise `null`.
36
41
  * @property {string} sourcePath Resource path or URL passed to the selected source.
37
42
  * @property {string|number|undefined} sourceRevision Caller content token.
38
43
  * @property {string} revisionKey Type-stable internal form of the content token.
@@ -165,6 +170,9 @@ class CjsResMan extends CjsEventEmitter {
165
170
  #reloadCandidates = new WeakMap();
166
171
  #reloadGenerations = new Map();
167
172
  #reloadOperations = new WeakMap();
173
+ #extensionRoutes = new Map();
174
+ #resourceExtensionRoutes = new WeakMap();
175
+ #resourceHandlerModes = new WeakMap();
168
176
 
169
177
  /**
170
178
  * Create a GPU-free resource manager and apply its initial registration.
@@ -226,6 +234,7 @@ class CjsResMan extends CjsEventEmitter {
226
234
  * @param {object} [options.cacheCleanup] Cleanup policy used if a smaller configured budget evicts cached identities.
227
235
  * @param {object} [options.mainThreadLoader] Direct execution strategy.
228
236
  * @param {object|Map<string,string>} [options.paths] Resource-prefix URL bases.
237
+ * @param {object|Map|object[]} [options.extensions] Extension route registrations.
229
238
  * @param {Function|null} [options.pathResolver] Optional complete resource-path-to-URL resolver.
230
239
  * @param {object} [options.workerLoader] Worker loader instance or construction options.
231
240
  * @param {boolean} [options.useWorkerLoading=true] Whether worker-backed execution is selected.
@@ -295,6 +304,14 @@ class CjsResMan extends CjsEventEmitter {
295
304
  for (const [ext, loader] of Object.entries(options.objectLoaders || {})) {
296
305
  this.RegisterObjectLoader(ext, loader);
297
306
  }
307
+ for (const [ext, registration] of normalizeExtensionRegistrationEntries(options.extensions)) {
308
+ const {
309
+ Handler,
310
+ handler,
311
+ ...routeOptions
312
+ } = registration;
313
+ this.RegisterExtension(ext, Handler || handler, routeOptions);
314
+ }
298
315
  return this;
299
316
  }
300
317
 
@@ -840,10 +857,7 @@ class CjsResMan extends CjsEventEmitter {
840
857
  if (!Array.isArray(Format.inputTypes) || Format.inputTypes.length === 0) {
841
858
  throw new TypeError(`${Format.name || "Format"} must declare non-empty inputTypes.`);
842
859
  }
843
- const descriptor = Object.freeze({
844
- Format,
845
- defaults: snapshotFormatDefaults(defaults)
846
- });
860
+ const descriptor = createFormatDescriptor(Format, defaults);
847
861
  for (const inputType of Format.inputTypes) {
848
862
  const key = normalizeResourceExtension(inputType);
849
863
  if (!key) continue;
@@ -855,6 +869,83 @@ class CjsResMan extends CjsEventEmitter {
855
869
  return this;
856
870
  }
857
871
 
872
+ /**
873
+ * Register one extension-selected resource or object handler and its reader
874
+ * route. A format constructor and an ordered format array are short forms
875
+ * for `{ Format }` and `{ Formats }` respectively.
876
+ *
877
+ * Re-registration replaces the complete route for future uncached handles.
878
+ * Existing handles retain the route snapshot captured at construction.
879
+ *
880
+ * @param {string} extension Input extension with or without a leading dot.
881
+ * @param {Function} Handler CjsResource-compatible handler constructor.
882
+ * @param {Function|Function[]|object} [formatOrFormatsOrOptions={}] Reader and optional target configuration.
883
+ * @returns {CjsResMan} This resource manager.
884
+ * @throws {TypeError} If the extension, handler, formats, or target policy is invalid.
885
+ */
886
+ RegisterExtension(extension, Handler, formatOrFormatsOrOptions = {}) {
887
+ const ext = normalizeResourceExtension(extension);
888
+ if (!ext) {
889
+ throw new TypeError("CjsResMan.RegisterExtension requires an extension.");
890
+ }
891
+ if (typeof Handler !== "function" || typeof Handler.prototype?.Initialize !== "function") {
892
+ throw new TypeError("CjsResMan.RegisterExtension requires a CjsResource-compatible handler constructor.");
893
+ }
894
+ const handlerMode = Handler.handlerMode;
895
+ if (!Object.values(ResourceHandlerMode).includes(handlerMode)) {
896
+ throw new TypeError("CjsResMan.RegisterExtension Handler.handlerMode must be ResourceHandlerMode.RESOURCE or ResourceHandlerMode.OBJECT.");
897
+ }
898
+ const options = normalizeExtensionRouteOptions(formatOrFormatsOrOptions);
899
+ const hasFormat = options.Format !== undefined || options.format !== undefined;
900
+ const hasFormats = options.Formats !== undefined || options.formats !== undefined;
901
+ if (hasFormat && hasFormats) {
902
+ throw new TypeError("CjsResMan.RegisterExtension accepts either Format or Formats, not both.");
903
+ }
904
+ const Target = options.Target || options.target || null;
905
+ const Identify = options.Identify || options.identify || null;
906
+ if (Target && Identify) {
907
+ throw new TypeError("CjsResMan.RegisterExtension accepts either Target or Identify, not both.");
908
+ }
909
+ if ((Target || Identify) && handlerMode !== ResourceHandlerMode.OBJECT) {
910
+ throw new TypeError("CjsResMan extension Target and Identify are valid only for object handlers.");
911
+ }
912
+ if (Target) assertExtensionTarget(Target, "Target");
913
+ if (Identify && typeof Identify !== "function") {
914
+ throw new TypeError("CjsResMan extension Identify must be a function.");
915
+ }
916
+ const explicitFormats = hasFormat ? [options.Format || options.format] : hasFormats ? options.Formats || options.formats : null;
917
+ if (explicitFormats !== null && !Array.isArray(explicitFormats)) {
918
+ throw new TypeError("CjsResMan extension Formats must be an array.");
919
+ }
920
+ const loader = explicitFormats === null ? this.GetObjectLoader(ext) : null;
921
+ const formats = explicitFormats === null ? loader ? [] : this.GetFormatDescriptors(ext) : explicitFormats.map((entry, index) => createExtensionFormatDescriptor(entry, options.defaults || {}, `Formats[${index}]`));
922
+ if (!loader && formats.length === 0) {
923
+ throw new TypeError(`CjsResMan extension .${ext} requires a Format, Formats, or registered reader.`);
924
+ }
925
+ validateOrderedExtensionFormats(formats, ext);
926
+ const route = Object.freeze({
927
+ extension: ext,
928
+ Handler,
929
+ handlerMode,
930
+ loader,
931
+ formats: Object.freeze([...formats]),
932
+ Target,
933
+ Identify
934
+ });
935
+ this.#extensionRoutes.set(ext, route);
936
+ return this;
937
+ }
938
+
939
+ /**
940
+ * Return the current immutable explicit route for one extension.
941
+ *
942
+ * @param {string} extension Input extension with or without a leading dot.
943
+ * @returns {object|null} Captured route descriptor, or `null`.
944
+ */
945
+ GetExtensionRoute(extension) {
946
+ return this.#extensionRoutes.get(normalizeResourceExtension(extension)) || null;
947
+ }
948
+
858
949
  /**
859
950
  * Return registered format facades for one normalized input extension.
860
951
  *
@@ -899,20 +990,21 @@ class CjsResMan extends CjsEventEmitter {
899
990
  const key = normalizeResourcePath(path);
900
991
  const variant = this.GetResourceVariant(options);
901
992
  const ext = normalizeResourceExtension(options.ext || getResourceExtension(key));
993
+ const cacheKey = getMotherLodeKey(key, variant);
994
+ const existing = this.motherLode.Lookup(cacheKey);
995
+ const extensionRoute = existing ? this.#resourceExtensionRoutes.get(existing) || null : this.GetExtensionRoute(ext);
902
996
  if (options.emit !== undefined) {
903
- const objectLoader = this.GetObjectLoader(ext);
997
+ const objectLoader = extensionRoute?.loader || !extensionRoute && this.GetObjectLoader(ext);
904
998
  if (objectLoader) {
905
999
  throw createObjectLoaderOutputMissingError(ext, options.emit);
906
1000
  } else {
907
- const descriptors = this.GetFormatDescriptors(ext);
1001
+ const descriptors = extensionRoute ? extensionRoute.formats : this.GetFormatDescriptors(ext);
908
1002
  const candidates = filterFormatDescriptors(descriptors, options);
909
1003
  if (descriptors.length > 0 && candidates.length === 0) {
910
1004
  throw createFormatOutputMissingError(ext, options.emit, descriptors);
911
1005
  }
912
1006
  }
913
1007
  }
914
- const cacheKey = getMotherLodeKey(key, variant);
915
- const existing = this.motherLode.Lookup(cacheKey);
916
1008
  if (existing && options.reload !== true) {
917
1009
  this.#BindResourceLifecycle(cacheKey, existing);
918
1010
  this.motherLode.KeepAlive?.(cacheKey);
@@ -923,8 +1015,8 @@ class CjsResMan extends CjsEventEmitter {
923
1015
  source: options.source || this.source
924
1016
  });
925
1017
  }
926
- const Constructor = this.ResolveResourceConstructor(options);
927
- const resource = this.CreateResource(Constructor, key, ext, options, existing && options.reload === true ? existing : null);
1018
+ const Constructor = this.ResolveResourceConstructor(options, extensionRoute);
1019
+ const resource = this.CreateResource(Constructor, key, ext, options, existing && options.reload === true ? existing : null, extensionRoute);
928
1020
  if (existing && options.reload === true) {
929
1021
  this.#BindResourceLifecycle(cacheKey, existing);
930
1022
  this.motherLode.KeepAlive?.(cacheKey);
@@ -996,7 +1088,7 @@ class CjsResMan extends CjsEventEmitter {
996
1088
  }
997
1089
  if (resource.HasPayload?.()) {
998
1090
  resource.KeepPayloadAlive?.();
999
- return Promise.resolve(getPublishedResourceObject(resource));
1091
+ return Promise.resolve(getPublishedResourceObject(resource, this.#resourceExtensionRoutes.get(resource) || null, this.#resourceHandlerModes.get(resource) || null));
1000
1092
  }
1001
1093
  const promise = this.QueueResourceObject(resource, operationOptions);
1002
1094
  const operation = {
@@ -1305,7 +1397,16 @@ class CjsResMan extends CjsEventEmitter {
1305
1397
  * resource manager.
1306
1398
  */
1307
1399
  Fetch(path, options = {}) {
1308
- return options.resource === true || options.requirement !== undefined || options.payload !== undefined ? this.FetchResource(path, options) : this.FetchObject(path, options);
1400
+ if (options.resource === true || options.requirement !== undefined || options.payload !== undefined) {
1401
+ return this.FetchResource(path, options);
1402
+ }
1403
+ const normalizedPath = normalizeResourcePath(path);
1404
+ const variant = this.GetResourceVariant(options);
1405
+ const cacheKey = getMotherLodeKey(normalizedPath, variant);
1406
+ const existing = this.motherLode.Lookup(cacheKey);
1407
+ const ext = normalizeResourceExtension(options.ext || getResourceExtension(normalizedPath));
1408
+ const route = existing ? this.#resourceExtensionRoutes.get(existing) || null : this.GetExtensionRoute(ext);
1409
+ return route?.handlerMode === ResourceHandlerMode.RESOURCE ? this.FetchResource(path, options) : this.FetchObject(path, options);
1309
1410
  }
1310
1411
 
1311
1412
  /**
@@ -1470,7 +1571,9 @@ class CjsResMan extends CjsEventEmitter {
1470
1571
  */
1471
1572
  async #PrepareResourceObject(resource, bytes, options, ownership) {
1472
1573
  this.#AssertOptionalResourceOwnership(ownership, "prepare:read");
1473
- const object = await this.ReadResourceObjectPayload(resource, bytes, options);
1574
+ const resolved = this.#ResolveResourceObjectRead(resource, bytes, options);
1575
+ const decoded = await this.#ReadResolvedResourceObjectPayload(resource, bytes, options, resolved);
1576
+ const object = this.#HydrateExtensionObject(resource, decoded, options, resolved);
1474
1577
  this.#AssertOptionalResourceOwnership(ownership, "prepare:read-settled");
1475
1578
  return this.#PublishResourceObject(ownership, resource, object, options);
1476
1579
  }
@@ -1490,7 +1593,8 @@ class CjsResMan extends CjsEventEmitter {
1490
1593
  let object = bytes;
1491
1594
  const resolved = this.#ResolveResourceObjectRead(resource, bytes, options);
1492
1595
  const formatOptions = resolved.descriptor ? createFormatReadOptions(resolved.descriptor, options) : null;
1493
- const runInWorker = Boolean(resolved.descriptor && typeof this.resourceLoader?.CanReadFormat === "function" && this.resourceLoader.CanReadFormat(resolved.descriptor, formatOptions));
1596
+ const formatContext = resolved.descriptor ? createResourcePathContext(resource.GetPath(), READ_CONTEXTS.get(options)) : null;
1597
+ const runInWorker = Boolean(resolved.descriptor && typeof this.resourceLoader?.CanReadFormat === "function" && this.resourceLoader.CanReadFormat(resolved.descriptor, formatOptions, formatContext));
1494
1598
  let read;
1495
1599
  if (runInWorker) {
1496
1600
  this.#AssertOptionalResourceOwnership(ownership, "worker-stage:read:run");
@@ -1512,7 +1616,8 @@ class CjsResMan extends CjsEventEmitter {
1512
1616
  this.#AssertOptionalResourceOwnership(ownership, "queue-stage:publish:enqueue");
1513
1617
  const publishTask = this.QueueTask(CjsResManQueue.MAIN, () => {
1514
1618
  this.#AssertOptionalResourceOwnership(ownership, "queue-stage:publish:run");
1515
- return this.#PublishResourceObject(ownership, resource, object, options);
1619
+ const hydrated = this.#HydrateExtensionObject(resource, object, options, resolved);
1620
+ return this.#PublishResourceObject(ownership, resource, hydrated, options);
1516
1621
  }, resource, {
1517
1622
  kind: "prepare",
1518
1623
  stage: "publish",
@@ -1546,25 +1651,31 @@ class CjsResMan extends CjsEventEmitter {
1546
1651
  * @param {CjsResource} resource Resource whose CPU outcome is requested.
1547
1652
  * @param {*} bytes Source input.
1548
1653
  * @param {object} options Requested output options.
1549
- * @returns {{loader: Function|null, descriptor: object|null}} Resolved reader.
1654
+ * @returns {{loader: Function|null, descriptor: object|null, route: object|null}} Resolved reader.
1550
1655
  */
1551
1656
  #ResolveResourceObjectRead(resource, bytes, options) {
1552
- const explicitLoader = this.GetObjectLoader(resource.GetExt());
1657
+ const route = this.#resourceExtensionRoutes.get(resource) || null;
1658
+ const explicitLoader = route?.loader || !route && this.GetObjectLoader(resource.GetExt());
1553
1659
  if (explicitLoader) {
1554
1660
  if (options.emit !== undefined) {
1555
1661
  throw createObjectLoaderOutputMissingError(resource.GetExt(), options.emit);
1556
1662
  }
1557
1663
  return {
1558
1664
  loader: explicitLoader,
1559
- descriptor: null
1665
+ descriptor: null,
1666
+ route
1560
1667
  };
1561
1668
  }
1562
1669
  return {
1563
1670
  loader: null,
1564
- descriptor: this.ResolveFormatDescriptor(resource.GetExt(), {
1671
+ descriptor: route ? resolveOrderedExtensionFormatDescriptor(route.formats, resource.GetExt(), {
1565
1672
  ...options,
1566
1673
  bytes
1567
- })
1674
+ }) : this.ResolveFormatDescriptor(resource.GetExt(), {
1675
+ ...options,
1676
+ bytes
1677
+ }),
1678
+ route
1568
1679
  };
1569
1680
  }
1570
1681
 
@@ -1584,6 +1695,56 @@ class CjsResMan extends CjsEventEmitter {
1584
1695
  return this.ReadFormatOnce(resource, resolved.descriptor, bytes, options);
1585
1696
  }
1586
1697
 
1698
+ /**
1699
+ * Apply a fixed target or dynamic identification policy after format reading
1700
+ * and before payload publication. Worker decoding has settled by this point,
1701
+ * so constructors and Identify functions remain on the caller thread.
1702
+ *
1703
+ * @param {CjsResource} resource Route handler receiving the decoded value.
1704
+ * @param {*} values Decoded format result.
1705
+ * @param {object} options Request options.
1706
+ * @param {{descriptor: object|null, route: object|null}} resolved Captured reader route.
1707
+ * @returns {*} Original or hydrated object value.
1708
+ */
1709
+ #HydrateExtensionObject(resource, values, options, resolved) {
1710
+ const route = resolved.route;
1711
+ if (!route || !route.Target && !route.Identify) return values;
1712
+ const Format = resolved.descriptor?.Format || null;
1713
+ const context = Object.freeze({
1714
+ ...createResourcePathContext(resource.GetPath(), READ_CONTEXTS.get(options)),
1715
+ Format,
1716
+ format: Format?.id || Format?.name || "",
1717
+ options,
1718
+ resMan: this
1719
+ });
1720
+ let Target = route.Target;
1721
+ if (route.Identify) {
1722
+ try {
1723
+ Target = route.Identify(values, context);
1724
+ } catch (error) {
1725
+ throw createExtensionTargetError(resource, "Identify threw an error.", error);
1726
+ }
1727
+ if (Target === true) return values;
1728
+ if (Target === false || Target === null || Target === undefined) {
1729
+ throw createExtensionTargetError(resource, "Identify did not resolve a target constructor.");
1730
+ }
1731
+ try {
1732
+ assertExtensionTarget(Target, "Identify result");
1733
+ } catch (error) {
1734
+ throw createExtensionTargetError(resource, "Identify returned an invalid target constructor.", error);
1735
+ }
1736
+ }
1737
+ try {
1738
+ const hydrated = typeof Target.fromYAML === "function" ? Target.fromYAML(values, context) : Target.from(values);
1739
+ if (hydrated && typeof hydrated.then === "function") {
1740
+ throw new TypeError("Extension target hydration must be synchronous.");
1741
+ }
1742
+ return hydrated;
1743
+ } catch (error) {
1744
+ throw createExtensionTargetError(resource, "Target hydration failed.", error);
1745
+ }
1746
+ }
1747
+
1587
1748
  /**
1588
1749
  * Publish the final CPU object outcome and loaded state. Every
1589
1750
  * CjsResource-compatible handle owns the reader/converter result through
@@ -1640,7 +1801,16 @@ class CjsResMan extends CjsEventEmitter {
1640
1801
  */
1641
1802
  #PublishResourceObjectValue(resource, object, options) {
1642
1803
  let result = object;
1643
- if (resource.constructor !== CjsResource && typeof resource.SetPayload === "function") {
1804
+ const route = this.#resourceExtensionRoutes.get(resource) || null;
1805
+ const handlerMode = route ? this.#resourceHandlerModes.get(resource) : null;
1806
+ if (handlerMode === ResourceHandlerMode.RESOURCE) {
1807
+ resource.SetPayload?.(object, options);
1808
+ resource.object = resource;
1809
+ result = resource;
1810
+ } else if (handlerMode === ResourceHandlerMode.OBJECT) {
1811
+ resource.SetPayload?.(object, options);
1812
+ resource.object = object;
1813
+ } else if (resource.constructor !== CjsResource && typeof resource.SetPayload === "function") {
1644
1814
  resource.SetPayload(object, options);
1645
1815
  resource.object = resource;
1646
1816
  result = resource;
@@ -1716,9 +1886,14 @@ class CjsResMan extends CjsEventEmitter {
1716
1886
  * @param {object} [options={}] Output, class, and `formatOptions` overrides.
1717
1887
  * @returns {Promise<*>} Parsed format outcome.
1718
1888
  * @throws {TypeError|Error} If the facade lacks a reader or reading fails.
1889
+ * @remarks Resource-backed calls pass normalized path context as the
1890
+ * reader's third argument; direct descriptor reads without a resource pass
1891
+ * `null`.
1719
1892
  */
1720
1893
  async ReadFormat(descriptor, bytes, options = {}) {
1721
- return this.resourceLoader.ReadFormat(descriptor, bytes, createFormatReadOptions(descriptor, options));
1894
+ const readContext = READ_CONTEXTS.get(options);
1895
+ const context = readContext ? createResourcePathContext(readContext.path, readContext) : null;
1896
+ return this.resourceLoader.ReadFormat(descriptor, bytes, createFormatReadOptions(descriptor, options), context);
1722
1897
  }
1723
1898
 
1724
1899
  /**
@@ -1986,11 +2161,19 @@ class CjsResMan extends CjsEventEmitter {
1986
2161
  const operationOptions = {
1987
2162
  ...options
1988
2163
  };
2164
+ const ext = normalizeResourceExtension(options.ext || getResourceExtension(normalizedPath));
2165
+ const fileName = getResourceFileName(normalizedPath);
2166
+ const requiresUrl = sourceRequiresUrl(source);
2167
+ const url = requiresUrl ? this.BuildUrl(normalizedPath) : null;
1989
2168
  const context = Object.freeze({
1990
2169
  resMan: this,
1991
2170
  source,
1992
2171
  path: normalizedPath,
1993
- sourcePath: sourceRequiresUrl(source) ? this.BuildUrl(normalizedPath) : normalizedPath,
2172
+ resFilePath: normalizedPath,
2173
+ ext,
2174
+ fileName,
2175
+ url,
2176
+ sourcePath: url || normalizedPath,
1994
2177
  sourceRevision,
1995
2178
  revisionKey
1996
2179
  });
@@ -2152,10 +2335,11 @@ class CjsResMan extends CjsEventEmitter {
2152
2335
  * @param {string} ext Normalized resource extension.
2153
2336
  * @param {object} [options={}] Constructor values, requested output, semantic requirement, and source provenance.
2154
2337
  * @param {object|Function|null} [disallowedAlias=null] Existing owner that a staged candidate must not reuse.
2338
+ * @param {object|null} [extensionRoute=null] Immutable explicit extension route captured for this handle.
2155
2339
  * @returns {CjsResource} Initialized resource-compatible handle.
2156
2340
  * @throws {TypeError|Error} If construction, candidate identity, or initialization is invalid.
2157
2341
  */
2158
- CreateResource(Constructor, path, ext, options = {}, disallowedAlias = null) {
2342
+ CreateResource(Constructor, path, ext, options = {}, disallowedAlias = null, extensionRoute = null) {
2159
2343
  const resource = new Constructor(options.values);
2160
2344
  if (!resource || typeof resource.Initialize !== "function") {
2161
2345
  throw new TypeError("Resource constructor must create a CjsResource-compatible object.");
@@ -2163,6 +2347,11 @@ class CjsResMan extends CjsEventEmitter {
2163
2347
  if (disallowedAlias && resource === disallowedAlias) {
2164
2348
  throw reloadCandidateAliasError(path, resource);
2165
2349
  }
2350
+ if (extensionRoute) {
2351
+ this.#resourceExtensionRoutes.set(resource, extensionRoute);
2352
+ const handlerMode = Constructor === extensionRoute.Handler ? extensionRoute.handlerMode : Object.values(ResourceHandlerMode).includes(Constructor.handlerMode) ? Constructor.handlerMode : ResourceHandlerMode.RESOURCE;
2353
+ this.#resourceHandlerModes.set(resource, handlerMode);
2354
+ }
2166
2355
  resource.Initialize(path, ext, normalizeRequirement(options.requirement || options.payload || ""));
2167
2356
  if (typeof resource.SetObjectLoader === "function") {
2168
2357
  const loaderOptions = getResourceLoaderOptions(options, options.source || this.source);
@@ -2177,13 +2366,15 @@ class CjsResMan extends CjsEventEmitter {
2177
2366
  * otherwise the generic `CjsResource` handle is used.
2178
2367
  *
2179
2368
  * @param {object} [options={}] Semantic requirement, payload, and emit request.
2369
+ * @param {object|null} [extensionRoute=null] Captured extension-selected default handler.
2180
2370
  * @returns {Function} Registered resource constructor or `CjsResource`.
2181
2371
  */
2182
- ResolveResourceConstructor(options = {}) {
2372
+ ResolveResourceConstructor(options = {}, extensionRoute = null) {
2183
2373
  const requested = normalizeRequirement(options.requirement || options.payload);
2184
2374
  if (requested && this.resourceTypes.has(requested)) return this.resourceTypes.get(requested);
2185
2375
  const emitted = normalizeRequirement(options.emit);
2186
2376
  if (emitted && this.resourceTypes.has(emitted)) return this.resourceTypes.get(emitted);
2377
+ if (extensionRoute) return extensionRoute.Handler;
2187
2378
  return CjsResource;
2188
2379
  }
2189
2380
 
@@ -2969,6 +3160,56 @@ function filterFormatDescriptors(descriptors, options) {
2969
3160
  return candidates;
2970
3161
  }
2971
3162
 
3163
+ /**
3164
+ * Resolve an ordered explicit extension route. Probes are evaluated in route
3165
+ * order and the sole unprobed final descriptor is the fallback. Selection is
3166
+ * final: reader failures are never interpreted as a reason to try another
3167
+ * format.
3168
+ *
3169
+ * @param {readonly object[]} descriptors Captured ordered route descriptors.
3170
+ * @param {string} ext Normalized resource extension.
3171
+ * @param {object} options Output filters and source bytes.
3172
+ * @returns {object} Selected immutable format descriptor.
3173
+ */
3174
+ function resolveOrderedExtensionFormatDescriptor(descriptors, ext, options) {
3175
+ const candidates = filterFormatDescriptors(descriptors, options);
3176
+ if (descriptors.length > 0 && candidates.length === 0 && options.emit !== undefined) {
3177
+ throw createFormatOutputMissingError(ext, options.emit, descriptors);
3178
+ }
3179
+ if (candidates.length === 0) {
3180
+ const error = new Error(`No format registered for .${ext}`);
3181
+ error.code = "CJS_RESOURCE_FORMAT_MISSING";
3182
+ error.ext = ext;
3183
+ throw error;
3184
+ }
3185
+ for (const descriptor of candidates) {
3186
+ const {
3187
+ Format,
3188
+ defaults
3189
+ } = descriptor;
3190
+ if (typeof Format.isSupported !== "function") return descriptor;
3191
+ const report = Format.isSupported(options.bytes, {
3192
+ ...defaults,
3193
+ ...(options.formatOptions || {})
3194
+ });
3195
+ if (isPositiveFormatProbe(report)) return descriptor;
3196
+ }
3197
+ const error = new Error(`No registered format supports the content for .${ext}`);
3198
+ error.code = "CJS_RESOURCE_FORMAT_UNSUPPORTED";
3199
+ error.ext = ext;
3200
+ error.formats = candidates.map(({
3201
+ Format
3202
+ }) => Format.name);
3203
+ throw error;
3204
+ }
3205
+
3206
+ /** Tests the supported forms returned by current format probes. */
3207
+ function isPositiveFormatProbe(report) {
3208
+ if (report === true) return true;
3209
+ if (!report || report === false || typeof report !== "object") return false;
3210
+ return report.supported === true || report.supported === "full" || report.supported === "partial";
3211
+ }
3212
+
2972
3213
  /**
2973
3214
  * Return one format facade's normal and diagnostic output declarations.
2974
3215
  *
@@ -3058,7 +3299,7 @@ function resolveFormatDescriptorCandidates(descriptors, ext, options) {
3058
3299
  ...defaults,
3059
3300
  ...(options.formatOptions || {})
3060
3301
  });
3061
- return report && report.supported !== false;
3302
+ return isPositiveFormatProbe(report);
3062
3303
  });
3063
3304
  if (supported.length === 1) candidates = supported;
3064
3305
  }
@@ -3104,24 +3345,61 @@ function normalizeResourceVariant(value) {
3104
3345
  function createPrepareContext(resMan, resource, bytes, options, stage) {
3105
3346
  return Object.freeze({
3106
3347
  ...options,
3348
+ ...createResourcePathContext(resource.GetPath(), READ_CONTEXTS.get(options)),
3107
3349
  stage,
3108
3350
  bytes,
3109
- path: resource.GetPath(),
3110
- ext: resource.GetExt(),
3111
3351
  resource,
3112
3352
  resMan
3113
3353
  });
3114
3354
  }
3115
3355
 
3356
+ /**
3357
+ * Create the immutable, source-neutral path context shared by format readers,
3358
+ * direct object loaders, target identification, and target hydration.
3359
+ *
3360
+ * @param {string} path Normalized or caller-supplied resource path.
3361
+ * @param {CjsResourceReadContext|null|undefined} [readContext] Active source read context.
3362
+ * @returns {Readonly<{path: string, resFilePath: string, ext: string, fileName: string, url: string|null}>}
3363
+ */
3364
+ function createResourcePathContext(path, readContext = null) {
3365
+ const resFilePath = normalizeResourcePath(path);
3366
+ return Object.freeze({
3367
+ path: resFilePath,
3368
+ resFilePath,
3369
+ ext: readContext?.ext ?? getResourceExtension(resFilePath),
3370
+ fileName: getResourceFileName(resFilePath),
3371
+ url: readContext?.url ?? null
3372
+ });
3373
+ }
3374
+
3375
+ /**
3376
+ * Return the normalized final component of a resource path without query or
3377
+ * fragment material.
3378
+ *
3379
+ * @param {string} path Resource path.
3380
+ * @returns {string} Lowercase normalized filename, or an empty string.
3381
+ */
3382
+ function getResourceFileName(path) {
3383
+ const normalized = normalizeResourcePath(path);
3384
+ const queryIndex = normalized.search(/[?#]/u);
3385
+ const cleanPath = queryIndex === -1 ? normalized : normalized.slice(0, queryIndex);
3386
+ const slashIndex = cleanPath.lastIndexOf("/");
3387
+ return cleanPath.slice(slashIndex + 1);
3388
+ }
3389
+
3116
3390
  /**
3117
3391
  * Recover the public object result represented by a resident resource payload.
3118
3392
  * The publication contract returns semantic subclasses as their resource
3119
3393
  * handle and base resources as their plain payload.
3120
3394
  *
3121
3395
  * @param {CjsResource} resource Resource with an attached CPU payload.
3396
+ * @param {object|null} route Captured explicit extension route.
3397
+ * @param {string|null} handlerMode Captured effective handler mode.
3122
3398
  * @returns {*} Resident public object outcome.
3123
3399
  */
3124
- function getPublishedResourceObject(resource) {
3400
+ function getPublishedResourceObject(resource, route = null, handlerMode = null) {
3401
+ if (route && handlerMode === ResourceHandlerMode.RESOURCE) return resource;
3402
+ if (route && handlerMode === ResourceHandlerMode.OBJECT) return resource.GetPayload();
3125
3403
  return resource.constructor !== CjsResource ? resource : resource.GetPayload();
3126
3404
  }
3127
3405
  function assertPositiveInteger(value, name) {
@@ -3490,5 +3768,91 @@ function normalizeRegistrationEntries(value, keyed = false) {
3490
3768
  });
3491
3769
  }
3492
3770
 
3771
+ /** Normalize `Register({ extensions })` into extension/registration pairs. */
3772
+ function normalizeExtensionRegistrationEntries(value) {
3773
+ if (value === null || value === undefined) return [];
3774
+ if (value instanceof Map) {
3775
+ return [...value.entries()].map(([ext, registration]) => [ext, normalizeExtensionRegistration(registration)]);
3776
+ }
3777
+ if (Array.isArray(value)) {
3778
+ return value.map((registration, index) => {
3779
+ if (!registration || typeof registration !== "object" || Array.isArray(registration)) {
3780
+ throw new TypeError(`CjsResMan extensions[${index}] must be an object.`);
3781
+ }
3782
+ const ext = registration.extension || registration.ext;
3783
+ if (!ext) {
3784
+ throw new TypeError(`CjsResMan extensions[${index}] requires extension or ext.`);
3785
+ }
3786
+ return [ext, normalizeExtensionRegistration(registration)];
3787
+ });
3788
+ }
3789
+ if (!value || typeof value !== "object") {
3790
+ throw new TypeError("CjsResMan extensions must be an object, Map, or array.");
3791
+ }
3792
+ return Object.entries(value).map(([ext, registration]) => [ext, normalizeExtensionRegistration(registration)]);
3793
+ }
3794
+ function normalizeExtensionRegistration(registration) {
3795
+ if (typeof registration === "function") return {
3796
+ Handler: registration
3797
+ };
3798
+ if (!registration || typeof registration !== "object" || Array.isArray(registration)) {
3799
+ throw new TypeError("CjsResMan extension registration must be a handler or object.");
3800
+ }
3801
+ return registration;
3802
+ }
3803
+ function normalizeExtensionRouteOptions(value) {
3804
+ if (typeof value === "function") return {
3805
+ Format: value
3806
+ };
3807
+ if (Array.isArray(value)) return {
3808
+ Formats: value
3809
+ };
3810
+ if (value === null || value === undefined) return {};
3811
+ if (!value || typeof value !== "object") {
3812
+ throw new TypeError("CjsResMan extension route must be a format, format array, or object.");
3813
+ }
3814
+ return value;
3815
+ }
3816
+ function createFormatDescriptor(Format, defaults = {}) {
3817
+ if (typeof Format !== "function") {
3818
+ throw new TypeError("CjsResMan format descriptor requires a format class.");
3819
+ }
3820
+ return Object.freeze({
3821
+ Format,
3822
+ defaults: snapshotFormatDefaults(defaults)
3823
+ });
3824
+ }
3825
+ function createExtensionFormatDescriptor(entry, defaults, label) {
3826
+ if (typeof entry === "function") return createFormatDescriptor(entry, defaults);
3827
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3828
+ throw new TypeError(`CjsResMan extension ${label} must be a format class or descriptor.`);
3829
+ }
3830
+ const Format = entry.Format || entry.format;
3831
+ return createFormatDescriptor(Format, entry.defaults === undefined ? defaults : entry.defaults);
3832
+ }
3833
+ function validateOrderedExtensionFormats(descriptors, ext) {
3834
+ for (let index = 0; index < descriptors.length - 1; index++) {
3835
+ const Format = descriptors[index].Format;
3836
+ if (typeof Format.isSupported !== "function") {
3837
+ throw new TypeError(`CjsResMan extension .${ext} format ${Format.name || index} has no support probe and must be last.`);
3838
+ }
3839
+ }
3840
+ }
3841
+ function assertExtensionTarget(Target, label) {
3842
+ if (typeof Target !== "function" || typeof Target.from !== "function" && typeof Target.fromYAML !== "function") {
3843
+ throw new TypeError(`CjsResMan extension ${label} must provide static from(values) or fromYAML(values, context).`);
3844
+ }
3845
+ }
3846
+ function createExtensionTargetError(resource, message, cause = null) {
3847
+ const path = getResourceDiagnosticPath(resource);
3848
+ const error = new Error(`CjsResMan extension target failed for ${path}: ${message}`, cause ? {
3849
+ cause
3850
+ } : undefined);
3851
+ error.code = "CJS_RESOURCE_EXTENSION_TARGET_FAILED";
3852
+ error.path = path;
3853
+ if (cause) error.cause = cause;
3854
+ return error;
3855
+ }
3856
+
3493
3857
  export { CjsResMan };
3494
3858
  //# sourceMappingURL=CjsResMan.js.map