renderers.mjs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { Model } from 'objection'
  2. import path from 'node:path'
  3. import fs from 'fs/promises'
  4. import { clone, filter, find, get, has, reverse, some, transform, union } from 'lodash-es'
  5. import yaml from 'js-yaml'
  6. import { DepGraph } from 'dependency-graph'
  7. import { parseModuleProps } from '../helpers/common.mjs'
  8. /**
  9. * Renderer model
  10. */
  11. export class Renderer extends Model {
  12. static get tableName() { return 'renderers' }
  13. static get jsonSchema () {
  14. return {
  15. type: 'object',
  16. required: ['module', 'isEnabled'],
  17. properties: {
  18. id: {type: 'string'},
  19. module: {type: 'string'},
  20. isEnabled: {type: 'boolean'}
  21. }
  22. }
  23. }
  24. static get jsonAttributes() {
  25. return ['config']
  26. }
  27. static async getRenderers() {
  28. return WIKI.db.renderers.query()
  29. }
  30. static async fetchDefinitions() {
  31. try {
  32. // -> Fetch definitions from disk
  33. const renderersDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/rendering'))
  34. WIKI.data.renderers = []
  35. for (const dir of renderersDirs) {
  36. const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/rendering', dir, 'definition.yml'), 'utf8')
  37. const defParsed = yaml.load(def)
  38. defParsed.key = dir
  39. defParsed.props = parseModuleProps(defParsed.props)
  40. WIKI.data.renderers.push(defParsed)
  41. WIKI.logger.debug(`Loaded renderers module definition ${dir}: [ OK ]`)
  42. }
  43. WIKI.logger.info(`Loaded ${WIKI.data.renderers.length} renderers module definitions: [ OK ]`)
  44. } catch (err) {
  45. WIKI.logger.error(`Failed to scan or load renderers providers: [ FAILED ]`)
  46. WIKI.logger.error(err)
  47. }
  48. }
  49. static async refreshRenderersFromDisk() {
  50. try {
  51. const dbRenderers = await WIKI.db.renderers.query()
  52. // -> Fetch definitions from disk
  53. await WIKI.db.renderers.fetchDefinitions()
  54. // -> Insert new Renderers
  55. const newRenderers = []
  56. let updatedRenderers = 0
  57. for (const renderer of WIKI.data.renderers) {
  58. if (!some(dbRenderers, ['module', renderer.key])) {
  59. newRenderers.push({
  60. module: renderer.key,
  61. isEnabled: renderer.enabledDefault ?? true,
  62. config: transform(renderer.props, (result, value, key) => {
  63. result[key] = value.default
  64. return result
  65. }, {})
  66. })
  67. } else {
  68. const rendererConfig = get(find(dbRenderers, ['module', renderer.key]), 'config', {})
  69. await WIKI.db.renderers.query().patch({
  70. config: transform(renderer.props, (result, value, key) => {
  71. if (!has(result, key)) {
  72. result[key] = value.default
  73. }
  74. return result
  75. }, rendererConfig)
  76. }).where('module', renderer.key)
  77. updatedRenderers++
  78. }
  79. }
  80. if (newRenderers.length > 0) {
  81. await WIKI.db.renderers.query().insert(newRenderers)
  82. WIKI.logger.info(`Loaded ${newRenderers.length} new renderers: [ OK ]`)
  83. }
  84. if (updatedRenderers > 0) {
  85. WIKI.logger.info(`Updated ${updatedRenderers} existing renderers: [ OK ]`)
  86. }
  87. // -> Delete removed Renderers
  88. for (const renderer of dbRenderers) {
  89. if (!some(WIKI.data.renderers, ['key', renderer.module])) {
  90. await WIKI.db.renderers.query().where('module', renderer.module).del()
  91. WIKI.logger.info(`Removed renderer ${renderer.module} because it is no longer present in the modules folder: [ OK ]`)
  92. }
  93. }
  94. } catch (err) {
  95. WIKI.logger.error('Failed to import renderers: [ FAILED ]')
  96. WIKI.logger.error(err)
  97. }
  98. }
  99. static async getRenderingPipeline(contentType) {
  100. const renderersDb = await WIKI.db.renderers.query().where('isEnabled', true)
  101. if (renderersDb && renderersDb.length > 0) {
  102. const renderers = renderersDb.map(rdr => {
  103. const renderer = find(WIKI.data.renderers, ['key', rdr.module])
  104. return {
  105. ...renderer,
  106. config: rdr.config
  107. }
  108. })
  109. // Build tree
  110. const rawCores = filter(renderers, renderer => !has(renderer, 'dependsOn')).map(core => {
  111. core.children = filter(renderers, ['dependsOn', core.key])
  112. return core
  113. })
  114. // Build dependency graph
  115. const graph = new DepGraph({ circular: true })
  116. rawCores.map(core => { graph.addNode(core.key) })
  117. rawCores.map(core => {
  118. rawCores.map(coreTarget => {
  119. if (core.key !== coreTarget.key) {
  120. if (core.output === coreTarget.input) {
  121. graph.addDependency(core.key, coreTarget.key)
  122. }
  123. }
  124. })
  125. })
  126. // Filter unused cores
  127. let activeCoreKeys = filter(rawCores, ['input', contentType]).map(core => core.key)
  128. clone(activeCoreKeys).map(coreKey => {
  129. activeCoreKeys = union(activeCoreKeys, graph.dependenciesOf(coreKey))
  130. })
  131. const activeCores = filter(rawCores, core => activeCoreKeys.includes(core.key))
  132. // Rebuild dependency graph with active cores
  133. const graphActive = new DepGraph({ circular: true })
  134. activeCores.map(core => { graphActive.addNode(core.key) })
  135. activeCores.map(core => {
  136. activeCores.map(coreTarget => {
  137. if (core.key !== coreTarget.key) {
  138. if (core.output === coreTarget.input) {
  139. graphActive.addDependency(core.key, coreTarget.key)
  140. }
  141. }
  142. })
  143. })
  144. // Reorder cores in reverse dependency order
  145. let orderedCores = []
  146. reverse(graphActive.overallOrder()).map(coreKey => {
  147. orderedCores.push(find(rawCores, ['key', coreKey]))
  148. })
  149. return orderedCores
  150. } else {
  151. WIKI.logger.error(`Rendering pipeline is empty!`)
  152. return false
  153. }
  154. }
  155. }