runtime-features.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. 'use strict'
  2. /** @typedef {`node:${string}`} NodeModuleName */
  3. /** @type {Record<NodeModuleName, () => any>} */
  4. const lazyLoaders = {
  5. __proto__: null,
  6. 'node:crypto': () => require('node:crypto'),
  7. 'node:sqlite': () => require('node:sqlite'),
  8. 'node:worker_threads': () => require('node:worker_threads'),
  9. 'node:zlib': () => require('node:zlib')
  10. }
  11. /**
  12. * @param {NodeModuleName} moduleName
  13. * @returns {boolean}
  14. */
  15. function detectRuntimeFeatureByNodeModule (moduleName) {
  16. try {
  17. lazyLoaders[moduleName]()
  18. return true
  19. } catch (err) {
  20. if (err.code !== 'ERR_UNKNOWN_BUILTIN_MODULE' && err.code !== 'ERR_NO_CRYPTO') {
  21. throw err
  22. }
  23. return false
  24. }
  25. }
  26. /**
  27. * @param {NodeModuleName} moduleName
  28. * @param {string} property
  29. * @returns {boolean}
  30. */
  31. function detectRuntimeFeatureByExportedProperty (moduleName, property) {
  32. const module = lazyLoaders[moduleName]()
  33. return typeof module[property] !== 'undefined'
  34. }
  35. const runtimeFeaturesByExportedProperty = /** @type {const} */ (['markAsUncloneable', 'zstd'])
  36. /** @type {Record<RuntimeFeatureByExportedProperty, [NodeModuleName, string]>} */
  37. const exportedPropertyLookup = {
  38. markAsUncloneable: ['node:worker_threads', 'markAsUncloneable'],
  39. zstd: ['node:zlib', 'createZstdDecompress']
  40. }
  41. /** @typedef {typeof runtimeFeaturesByExportedProperty[number]} RuntimeFeatureByExportedProperty */
  42. const runtimeFeaturesAsNodeModule = /** @type {const} */ (['crypto', 'sqlite'])
  43. /** @typedef {typeof runtimeFeaturesAsNodeModule[number]} RuntimeFeatureByNodeModule */
  44. const features = /** @type {const} */ ([
  45. ...runtimeFeaturesAsNodeModule,
  46. ...runtimeFeaturesByExportedProperty
  47. ])
  48. /** @typedef {typeof features[number]} Feature */
  49. /**
  50. * @param {Feature} feature
  51. * @returns {boolean}
  52. */
  53. function detectRuntimeFeature (feature) {
  54. if (runtimeFeaturesAsNodeModule.includes(/** @type {RuntimeFeatureByNodeModule} */ (feature))) {
  55. return detectRuntimeFeatureByNodeModule(`node:${feature}`)
  56. } else if (runtimeFeaturesByExportedProperty.includes(/** @type {RuntimeFeatureByExportedProperty} */ (feature))) {
  57. const [moduleName, property] = exportedPropertyLookup[feature]
  58. return detectRuntimeFeatureByExportedProperty(moduleName, property)
  59. }
  60. throw new TypeError(`unknown feature: ${feature}`)
  61. }
  62. /**
  63. * @class
  64. * @name RuntimeFeatures
  65. */
  66. class RuntimeFeatures {
  67. /** @type {Map<Feature, boolean>} */
  68. #map = new Map()
  69. /**
  70. * Clears all cached feature detections.
  71. */
  72. clear () {
  73. this.#map.clear()
  74. }
  75. /**
  76. * @param {Feature} feature
  77. * @returns {boolean}
  78. */
  79. has (feature) {
  80. return (
  81. this.#map.get(feature) ?? this.#detectRuntimeFeature(feature)
  82. )
  83. }
  84. /**
  85. * @param {Feature} feature
  86. * @param {boolean} value
  87. */
  88. set (feature, value) {
  89. if (features.includes(feature) === false) {
  90. throw new TypeError(`unknown feature: ${feature}`)
  91. }
  92. this.#map.set(feature, value)
  93. }
  94. /**
  95. * @param {Feature} feature
  96. * @returns {boolean}
  97. */
  98. #detectRuntimeFeature (feature) {
  99. const result = detectRuntimeFeature(feature)
  100. this.#map.set(feature, result)
  101. return result
  102. }
  103. }
  104. const instance = new RuntimeFeatures()
  105. module.exports.runtimeFeatures = instance
  106. module.exports.default = instance