UNPKG

@servable/tools

Version:

Servable tools is a utility that builds a protocol's manifest and documents it.

931 lines (715 loc) 22 kB
import BaseClass from './base.js' import directories from '../../lib/directories.js' import cleanProtocols from '../../lib/cleanProtocols.js' import mergeClassProtocols from './lib/mergeClassProtocols.js' import importJSONAsync from '../../lib/importJSONAsync.js' import directoryGlob from '../../lib/directoryGlob.js' import checkFileExists from '../../lib/checkFileExists.js' import directoryFilesRecursive from '../../lib/directoryFilesRecursive.js' import { ProtocolEnum } from '../../manifest/data/1.0.0/enums.js' import triggerItems from './lib/triggerItems.js' import fs from 'fs' import fspath from 'path' import semver from 'semver' export default class ProtocolLoaderV1_1_0 extends BaseClass { // #region class async getClass({ className }) { const cacheKey = 'class' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/models/${className.toLowerCase()}/class.js` if (!(await checkFileExists(path))) { return null } return this._importJSDefault({ path, }) } async classFunctions({ className }) { const cacheKey = 'classFunctions' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } let path = `${this.path}/models/${className.toLowerCase()}/functions` if (!(await checkFileExists(path))) { path = `${this.path}/models/${className.toLowerCase()}/cloudCode` if (!(await checkFileExists(path))) { return null } } const data = (await directoryFilesRecursive({ path })) return data } async classTriggers({ className }) { const cacheKey = 'classTriggers' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const prefix = `${this.path}/models/${className.toLowerCase()}/triggers` const data = {} for (var i = 0; i < triggerItems.length; i++) { const item = triggerItems[i] const path = `${prefix}/${item}.js` if (!(await checkFileExists(path))) { continue } const itemData = (await import(path)) if (!itemData || !itemData.default) { continue } data[item] = itemData.default } if (Object.keys(data).length > 0) { return data } return this.classTriggersMerged({ className }) } async classTriggersMerged({ className }) { const cacheKey = 'classTriggers' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/models/${className.toLowerCase()}/triggers/index.js` if (!(await checkFileExists(path))) { return null } const data = (await import(path)) //this.cache[cacheKey] = data return data } async classJobs({ className }) { const cacheKey = 'classJobs' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/models/${className.toLowerCase()}/jobs` if (!(await checkFileExists(path))) { return null } const data = (await directoryFilesRecursive({ path })) //this.cache[cacheKey] = data return data } classSeedFolder({ className }) { return `${this.path}/models/${className.toLowerCase()}/seed` } async classSeedMode(props) { const cacheKey = 'classSeedMode' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const classSeedFolder = this.classSeedFolder(props) let _path = `${classSeedFolder}/index.js` if ((await checkFileExists(_path))) { return 'manual' } _path = `${classSeedFolder}/transformer.js` if ((await checkFileExists(_path))) { return 'auto' } return 'none' } async classSeedManual(props) { const cacheKey = 'classSeedManual' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const classSeedFolder = this.classSeedFolder(props) let _path = `${classSeedFolder}/index.js` if (!(await checkFileExists(_path))) { return null } const data = (await import(_path)).default //this.cache[cacheKey] = data return data } async classSeedMetadata(props) { const cacheKey = 'classSeedMetadata' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const classSeedFolder = this.classSeedFolder(props) const _path = `${classSeedFolder}/metadata.json` if (!(await checkFileExists(_path))) { return null } const data = await importJSONAsync(_path) //this.cache[cacheKey] = data return data } async classSeedAutoFiles(props) { const { className } = props const cacheKey = 'classSeedAutoFiles' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = this.classSeedFolder({ className }) const result = {} let _path = `${path}/data.json` if ((await checkFileExists(_path))) { result.data = await importJSONAsync(_path) } _path = `${path}/transformer.js` if ((await checkFileExists(_path))) { result.transformer = (await import(_path)).default // console.log("[Servable]", result.transformer) } _path = `${path}/validator.js` if ((await checkFileExists(_path))) { result.validator = (await import(_path)).default } _path = `${path}/ref.js` if ((await checkFileExists(_path))) { result.uniqueRef = (await import(_path)).default } return result } classConfigFolder({ className }) { return `${this.path}/models/${className.toLowerCase()}/config` } async classConfigDataFiles({ className }) { const cacheKey = 'classConfigDataFiles' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = this.classConfigFolder({ className }) const result = {} const valuesPath = `${path}/entries.json` if ((await checkFileExists(valuesPath))) { result.entries = await importJSONAsync(valuesPath) } const conditionsPath = `${path}/conditions.json` if ((await checkFileExists(conditionsPath))) { result.conditions = await importJSONAsync(conditionsPath) } const groupsPath = `${path}/groups.json` if ((await checkFileExists(groupsPath))) { result.groups = await importJSONAsync(groupsPath) } return result } async classProtocols({ className, withProtocolsProtocols = false }) { const cacheKey = 'class' // //console.log('classProtocols>', className, 'enter') if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const items = [] const seedMode = await this.classSeedMode({ className }) // //console.log('classProtocols>', className, 'seedmode', seedMode) if (seedMode === 'auto') { items.push({ id: 'servableautoseedable' }) } const _class = await this.getClass({ className }) if (!_class) { //console.log('classProtocols>', className, 'no _class') return items } let data = mergeClassProtocols({ items, _class, withProtocolsProtocols }) const path = `${this.path}/models/${className.toLowerCase()}/protocols.js` if ((await checkFileExists(path))) { let _data = (await import(path)).default // //console.log('classProtocols>', className, '_data', _data) _data = _data ? _data : [] data = [...data, ..._data] } //this.cache[cacheKey] = data data = cleanProtocols(data) // result = _.uniq(result, a => a.id) // //console.log('classProtocols>', className, 'finaldata', data) return data } // #endregion async afterInit() { const main = await this.main() return main ? main.afterInit : null } async beforeInit() { const main = await this.main() return main ? main.beforeInit : null } async beforeEnd() { const main = await this.main() return main ? main.beforeEnd : null } async main() { const path = `${this.path}/main.js` if (!(await checkFileExists(path))) { return null } const a = await this._importJSDefault({ path, }) return a } configFolder() { return `${this.path}/config` } async configDataFiles() { const cacheKey = 'configData' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = this.configFolder() const result = {} const valuesPath = `${path}/entries.json` if ((await checkFileExists(valuesPath))) { result.entries = await importJSONAsync(valuesPath) } const conditionsPath = `${path}/conditions.json` if ((await checkFileExists(conditionsPath))) { result.conditions = await importJSONAsync(conditionsPath) } const groupsPath = `${path}/groups.json` if ((await checkFileExists(groupsPath))) { result.groups = await importJSONAsync(groupsPath) } return result } seedFolder() { return `${this.path}/seed` } async seedMode() { const cacheKey = 'seedMode' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const seedFolder = this.seedFolder() let _path = `${seedFolder}/index.js` if (await checkFileExists(_path)) { return 'manual' } //Auto not handled yet for protocols // _path = `${seedFolder}/transformer.js` // if (await checkFileExists(_path)) { // return 'auto' // } return 'none' } async seedManual() { const cacheKey = 'seedManual' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } let _path = `${this.path}/seed/index.js` if (!(await checkFileExists(_path))) { return null } const data = (await import(_path)).default //this.cache[cacheKey] = data return data } async seedMetadata() { const cacheKey = 'seedMetadata' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.seedFolder()}/metadata.json` if (!(await checkFileExists(path))) { return null } const data = await importJSONAsync(path) //this.cache[cacheKey] = data return data } async triggers() { const cacheKey = 'triggers' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const prefix = `${this.path}/target/triggers` const data = {} for (var i = 0; i < triggerItems.length; i++) { const item = triggerItems[i] const path = `${prefix}/${item}.js` if (!(await checkFileExists(path))) { continue } const itemData = (await import(path)) if (!itemData || !itemData.default) { continue } data[item] = itemData.default } if (Object.keys(data).length > 0) { return data } return this.triggersMerged() } async triggersMerged() { const cacheKey = 'triggers' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/target/triggers/index.js` if (!(await checkFileExists(path))) { return null } const data = (await import(path)) // this.cache[cacheKey] = data return data } async triggersMetadata() { const cacheKey = 'triggersMetadata' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/target/triggers/metadata.json` if (!(await checkFileExists(path))) { return null } const data = await importJSONAsync(path) this.cache[cacheKey] = data return data } //#region schema async classesSchemas(props) { // if (this.valueInCache('classes')) { // return this.valueInCache('classes') // } const schema = await this.schemaRaw(props) if (schema && schema.managed) { return schema.managed.classes } const schemaPath = await this._schemaPath() let path = `${schemaPath}/classes.js` if ((await checkFileExists(path))) { const classes = await (await import(path)).default({ protocol: this.protocolInstance }) // this.cache['classes'] = classes return classes } path = `${schemaPath}/classes.json` if (!(await checkFileExists(path))) { return [] } const classes = await importJSONAsync(path) // this.cache['classes'] = classes return classes } async schemaFields(props) { // const cacheKey = 'schemaFields' // if (this.valueInCache(cacheKey)) { // return this.valueInCache(cacheKey) // } const schema = await this.schemaRaw(props) if (schema && schema.target) { return schema.target.fields } const schemaPath = await this._schemaPath() const path = `${schemaPath}/fields.json` if (!(await checkFileExists(path))) { return null } return this._importJSONDefault({ path, }) } async schemaIndexes(props) { // const cacheKey = 'schemaIndexes' // if (this.valueInCache(cacheKey)) { // return this.valueInCache(cacheKey) // } const schema = await this.schemaRaw(props) if (schema && schema.target) { return schema.target.indexes } const schemaPath = await this._schemaPath() const path = `${schemaPath}/indexes.json` if (!(await checkFileExists(path))) { return null } return this._importJSONDefault({ path, }) } async schemaRaw(props = {}) { const { ad } = props const a = await this._accessManifestItem({ item: ProtocolEnum.Schema, }) // const cacheKey = 'schemaIndexes' // if (this.valueInCache(cacheKey)) { // return this.valueInCache(cacheKey) // } const schemaPath = await this._schemaPath() //#TODO: A dynamic schema needs protocols to be instantiated per class as to avoid class defintion collusions and edge case. // let path = `${schemaPath}/index.js` // if ((await checkFileExists(path))) { // let data = (await import(path)).default // //#TODO: don't use up protocol params // data = await data({ ...props, params: this.protocolInstance.params }) // return data // } let path = `${schemaPath}/index.json` if (!(await checkFileExists(path))) { return null } const b = await this._importJSONDefault({ path, }) return b } async schemaClassLevelPermissions(props) { // const cacheKey = 'schemaClassLevelPermissions' // if (this.valueInCache(cacheKey)) { // return this.valueInCache(cacheKey) // } const schema = await this.schemaRaw(props) if (schema && schema.target) { return schema.target.classLevelPermissions } const schemaPath = await this._schemaPath() const path = `${schemaPath}/classlevelpermissions.json` if (!(await checkFileExists(path))) { return null } return this._importJSONDefault({ path, }) } async _schemaPath() { //let version = this.currentProtocolVersion ? this.currentProtocolVersion : this.protocolInstanceVersion let version = this.protocolInstance.version let versions = await this.schemaVersions() if (!version) { return `${this.path}/schema` } if (!versions || !versions.length) { return `${this.path}/schema` } if (versions && versions.length && !versions.includes(version)) { return `${this.path}/schema` } if (versions && versions.length && version === 'latest') { version = versions[versions.length - 1] //#TODO: sort by semver } const path = `${this.path}/schema/${version}` return path } async schemaVersions() { const cacheKey = '_schemaVersions' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const items = await directories({ path: `${this.path}/schema` }) if (!items || !items.length) { return null } let data = items.map(i => i.name) data = data.sort() this.cache[cacheKey] = data return data } async schemaVersionOf({ version, subPath }) { const cacheKey = `_schemaVersionOf${subPath}` if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const versions = await this.schemaVersions() if (!versions || !versions.includes(version)) { return null } const path = `${this.path}/schema/${version}/${subPath}.js` if (!(await checkFileExists(path))) { return null } const data = await import(path) this.cache[cacheKey] = data return data } //#endregion async ownProtocols() { const cacheKey = 'ownProtocols' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } let path = `${this.path}/target/protocols.js` if (!(await checkFileExists(path))) { return null } const data = (await import(path)).default //this.cache[cacheKey] = data return data } async ownProtocolsClass() { const cacheKey = 'ownProtocolsClass' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/target/class.js` if (!(await checkFileExists(path))) { return null } // const _data = (await import(path)) return this._importJSDefault({ path, }) } async liveClasses() { const cacheKey = 'liveclasses' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } //#TODO const main = await this.main() if (!main) { return [] } return main.liveClasses } //#region system async systemDockerCompose() { const cacheKey = 'systemDockerCompose' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/system/docker/docker-compose.yaml` if (!(await checkFileExists(path))) { return null } const data = (await import(path)) //this.cache[cacheKey] = data return data } async systemDockerComposeExists() { const path = this.systemDockerComposePath() return checkFileExists(path) } systemDockerComposePath() { return `${this.systemDockerComposeDirPath()}/docker-compose.yaml` } systemDockerDataPath() { return `${this.systemDockerComposeDirPath()}/data` } systemDockerComposeDirPath() { return `${this.path}/system/docker` } configDirPath_obs() { return `${this.path}/config` } async configData_obs() { // const cacheKey = 'schemaClassLevelPermissions' // if (this.valueInCache(cacheKey)) { // return this.valueInCache(cacheKey) // } const schemaPath = await this._schemaPath() const path = `${this.configDirPath_obs()}/index.json` if (!(await checkFileExists(path))) { return null } return this._importJSONDefault({ path, }) } async systemDockerPayloadAdapter() { const cacheKey = 'systemDockerPayloadAdapter' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/system/docker/adaptpayload.js` if (!(await checkFileExists(path))) { return null } return this._importJSDefault({ path, }) } //#endregion async functions() { const cacheKey = 'functions' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } let path = `${this.path}/cloudCode` if (!(await checkFileExists(path))) { path = `${this.path}/functions` if (!(await checkFileExists(path))) { path = `${this.path}/cloudcode` if (!(await checkFileExists(path))) { return null } } } const data = (await directoryFilesRecursive({ path })) //this.cache[cacheKey] = data return data } async services() { let path = `${this.path}/services` if (!(await checkFileExists(path))) { return null } const data = (await directoryFilesRecursive({ path })) //this.cache[cacheKey] = data return data } async routes() { let path = `${this.path}/routes` if (!(await checkFileExists(path))) { return null } let data = {} const items = await fs.promises.readdir(path) if (!items || !items.length) { return null } for (let i = 0; i < items.length; i++) { const item = items[i] if (!item) { continue } if (item[0] !== 'v') { continue } if (!semver.valid(semver.coerce(item))) { continue } const __path = fspath.join(path, item) const _stat = await fs.promises.stat(__path) if (!_stat) { continue } const isDir = _stat.isDirectory() if (!isDir) { continue } let _data = (await directoryGlob({ path: `${__path}/**/*.js`, globOptions: { mark: true, ignore: ['**/lib/**'] } })) data[item] = _data } return data } async targetRoutes() { //#TODO: let path = `${this.path}/target/routes` if (!(await checkFileExists(path))) { return null } const data = (await directoryGlob({ path: `${path}/**/*.js`, globOptions: { mark: true, ignore: ['**/lib/**'] } })) return data } async liveQueries() { let path = `${this.path}/livequeries` if (!(await checkFileExists(path))) { return null } const data = (await directoryGlob({ path: `${path}/**/*.js`, globOptions: { mark: true, ignore: ['**/lib/**'] } })) return data } async jobFiles() { const cacheKey = 'jobFiles' if (this._valueInCache(cacheKey)) { return this._valueInCache(cacheKey) } const path = `${this.path}/jobs` if (!(await checkFileExists(path))) { return null } const data = (await directoryFilesRecursive({ path })) //this.cache[cacheKey] = data return data } }