From f54ed16806cb1917664db358fd2ef4b3046227e8 Mon Sep 17 00:00:00 2001 From: Jeremy LaCivita Date: Mon, 3 Jun 2024 13:30:16 -0400 Subject: [PATCH] chore: Provider updates --- .../javascript/src/shared/Gateway/Server.mjs | 21 +- .../javascript/src/shared/Gateway/index.mjs | 4 +- .../templates/declarations/event.js | 4 +- .../imports/event-based-provider.mjs | 2 + .../javascript/templates/imports/provider.mjs | 3 +- .../templates/initializations/provider.mjs | 2 +- .../templates/interfaces/default.mjs | 2 +- .../templates/interfaces/focusable.mjs | 2 +- .../javascript/templates/methods/provide.js | 6 +- .../templates/methods/registration.js | 4 + .../templates/sections/provider-interfaces.js | 8 - src/cli.mjs | 2 - src/macrofier/engine.mjs | 192 +++++++++++------- src/macrofier/index.mjs | 9 +- src/openrpc/index.mjs | 2 +- src/shared/json-schema.mjs | 1 + src/shared/methods.mjs | 9 +- src/shared/modules.mjs | 148 ++++++++++++-- 18 files changed, 305 insertions(+), 116 deletions(-) create mode 100644 languages/javascript/templates/imports/event-based-provider.mjs create mode 100644 languages/javascript/templates/methods/registration.js diff --git a/languages/javascript/src/shared/Gateway/Server.mjs b/languages/javascript/src/shared/Gateway/Server.mjs index a5942eb0..84ef0570 100644 --- a/languages/javascript/src/shared/Gateway/Server.mjs +++ b/languages/javascript/src/shared/Gateway/Server.mjs @@ -52,9 +52,8 @@ export async function notify(method, params) { } // Register a provider implementation with an interface name -export function provide(interfaceName, provider, methods) { +export function provide(interfaceName, provider) { providers[interfaceName] = provider - interfaces[interfaceName] = methods } // Register a notification listener with an event name @@ -66,6 +65,22 @@ export function unsubscribe(event) { delete listeners[event] } +export function registerProviderInterface(capability, _interface, method, parameters, response, focusable) { + interfaces[_interface] = interfaces[_interface] || { + capability, + name: _interface, + methods: [], + } + + interfaces[_interface].methods.push({ + name: method, + parameters, + response, + focusable + }) +} + + async function getProviderResult(method, params) { const split = method.split('.') method = split.pop() @@ -74,7 +89,7 @@ async function getProviderResult(method, params) { if (providers[interfaceName]) { if (providers[interfaceName][method]) { // sort the params into an array based on the interface parameter order - const parameters = interfaces[interfaceName].find(m => m.name === method).parameters.map(p => params[p]).filter(p => p !== undefined) + const parameters = interfaces[interfaceName].methods.find(m => m.name === method).parameters.map(p => params[p]).filter(p => p !== undefined) return await providers[interfaceName][method](...parameters) } throw `Method not implemented: ${method}` diff --git a/languages/javascript/src/shared/Gateway/index.mjs b/languages/javascript/src/shared/Gateway/index.mjs index fc86a782..a15bd65b 100644 --- a/languages/javascript/src/shared/Gateway/index.mjs +++ b/languages/javascript/src/shared/Gateway/index.mjs @@ -57,8 +57,8 @@ export function unsubscribe(event) { Server.subscribe(event) } -export function provide(interfaceName, provider, methods) { - Server.provide(interfaceName, provider, methods) +export function provide(interfaceName, provider) { + Server.provide(interfaceName, provider) } export function deprecate (method, alternative) { diff --git a/languages/javascript/templates/declarations/event.js b/languages/javascript/templates/declarations/event.js index ce945d84..f52fa2a9 100644 --- a/languages/javascript/templates/declarations/event.js +++ b/languages/javascript/templates/declarations/event.js @@ -5,7 +5,7 @@ * @param {Function} callback ${if.deprecated} * @deprecated ${method.deprecation} ${end.if.deprecated} */ - function listen(event: '${event.name}'${if.context}, ${event.signature.params}${end.if.context}, callback: (data: ${notifier.value.type}) => void): Promise + function listen(event: '${event.name}'${if.context}, ${event.signature.params}${end.if.context}, callback: (data: ${event.result.type}) => void): Promise /** * ${method.summary} @@ -15,4 +15,4 @@ ${end.if.deprecated} */ * @param {Function} callback ${if.deprecated} * @deprecated ${method.deprecation} ${end.if.deprecated} */ -function once(event: '${event.name}'${if.context}, ${event.signature.params}${end.if.context}, callback: (data: ${notifier.value.type}) => void): Promise +function once(event: '${event.name}'${if.context}, ${event.signature.params}${end.if.context}, callback: (data: ${event.result.type}) => void): Promise diff --git a/languages/javascript/templates/imports/event-based-provider.mjs b/languages/javascript/templates/imports/event-based-provider.mjs new file mode 100644 index 00000000..b3ea7b4f --- /dev/null +++ b/languages/javascript/templates/imports/event-based-provider.mjs @@ -0,0 +1,2 @@ +import ProvideManager from '../ProvideManager/index.mjs' +import { registerProviderInterface } from '../ProvideManager/index.mjs' diff --git a/languages/javascript/templates/imports/provider.mjs b/languages/javascript/templates/imports/provider.mjs index b3ea7b4f..37be97f3 100644 --- a/languages/javascript/templates/imports/provider.mjs +++ b/languages/javascript/templates/imports/provider.mjs @@ -1,2 +1 @@ -import ProvideManager from '../ProvideManager/index.mjs' -import { registerProviderInterface } from '../ProvideManager/index.mjs' +import { registerProviderInterface } from '../Gateway/Server.mjs' diff --git a/languages/javascript/templates/initializations/provider.mjs b/languages/javascript/templates/initializations/provider.mjs index b113d236..9416fb09 100644 --- a/languages/javascript/templates/initializations/provider.mjs +++ b/languages/javascript/templates/initializations/provider.mjs @@ -1 +1 @@ -registerProviderInterface('${capability}', '${info.title}', ${interface}) +registerProviderInterface('${capability}', '${interface}', '${method.name}', ${method.params.array}, ${method.response}, ${method.focusable}) diff --git a/languages/javascript/templates/interfaces/default.mjs b/languages/javascript/templates/interfaces/default.mjs index 63f63093..34ffa217 100644 --- a/languages/javascript/templates/interfaces/default.mjs +++ b/languages/javascript/templates/interfaces/default.mjs @@ -1 +1 @@ - ${method.name}(${method.signature.params}, session: ProviderSession): Promise<${method.result.type}> + ${method.name}(${method.signature.params}): Promise<${method.result.type}> diff --git a/languages/javascript/templates/interfaces/focusable.mjs b/languages/javascript/templates/interfaces/focusable.mjs index b81737b1..34ffa217 100644 --- a/languages/javascript/templates/interfaces/focusable.mjs +++ b/languages/javascript/templates/interfaces/focusable.mjs @@ -1 +1 @@ - ${method.name}(${method.signature.params}, session: FocusableProviderSession): Promise<${method.result.type}> + ${method.name}(${method.signature.params}): Promise<${method.result.type}> diff --git a/languages/javascript/templates/methods/provide.js b/languages/javascript/templates/methods/provide.js index 8cb48885..f2dcf0a7 100644 --- a/languages/javascript/templates/methods/provide.js +++ b/languages/javascript/templates/methods/provide.js @@ -1,3 +1,3 @@ -function provide(capability, provider) { - return ProvideManager.provide(capability, provider) -} +// function provide(capability, provider) { +// return ProvideManager.provide(capability, provider) +// } diff --git a/languages/javascript/templates/methods/registration.js b/languages/javascript/templates/methods/registration.js new file mode 100644 index 00000000..841991e8 --- /dev/null +++ b/languages/javascript/templates/methods/registration.js @@ -0,0 +1,4 @@ + +function ${method.name}(provider) { + return Gateway.provide('${info.title}', provider) + } \ No newline at end of file diff --git a/languages/javascript/templates/sections/provider-interfaces.js b/languages/javascript/templates/sections/provider-interfaces.js index 0b77c87d..d897cb7d 100644 --- a/languages/javascript/templates/sections/provider-interfaces.js +++ b/languages/javascript/templates/sections/provider-interfaces.js @@ -1,11 +1,3 @@ // Provider Interfaces -interface ProviderSession { - correlationId(): string // Returns the correlation id of the current provider session -} - -interface FocusableProviderSession extends ProviderSession { - focus(): Promise // Requests that the provider app be moved into focus to prevent a user experience -} - ${providers.list} \ No newline at end of file diff --git a/src/cli.mjs b/src/cli.mjs index 63329529..d0ea252a 100755 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -52,8 +52,6 @@ const parsedArgs = Object.assign({}, defaults, nopt(knownOpts, shortHands, proce const task = process.argv[2] const signOff = () => console.log('\nThis has been a presentation of \x1b[38;5;202mFirebolt\x1b[0m \u{1F525} \u{1F529}\n') -console.dir(parsedArgs) - try { if (task === 'slice') { await slice(parsedArgs).then(signOff) diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index 499a73de..9ddca1fc 100644 --- a/src/macrofier/engine.mjs +++ b/src/macrofier/engine.mjs @@ -29,8 +29,8 @@ import isString from 'crocks/core/isString.js' import predicates from 'crocks/predicates/index.js' const { isObject, isArray, propEq, pathSatisfies, propSatisfies } = predicates -import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, isAllowFocusMethod, hasAllowFocusMethods, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod } from '../shared/modules.mjs' -import { getNotifier, name as methodName } from '../shared/methods.mjs' +import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, isAllowFocusMethod, hasAllowFocusMethods, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod, getProvidedInterfaces } from '../shared/modules.mjs' +import { getNotifier, name as methodName, name, provides } from '../shared/methods.mjs' import isEmpty from 'crocks/core/isEmpty.js' import { getReferencedSchema, getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema, mergeAnyOf, mergeOneOf, getSafeEnumKeyName, getAllValuesForName } from '../shared/json-schema.mjs' @@ -508,6 +508,7 @@ return obj const generateMacros = (server, client, templates, languages, options = {}) => { // TODO: figure out anyOfs/polymorphs on the client RPC. It can work for events, but not providers + if (options.createPolymorphicMethods) { let methods = [] server.methods && server.methods.forEach(method => { @@ -555,14 +556,11 @@ const generateMacros = (server, client, templates, languages, options = {}) => { macros.callsMetrics = true } - // need to call this twice in a row, so building a shorthand here - const _generateSchemas = (document) => generateSchemas(document, templates, { baseUrl: '' }).filter(s => (options.copySchemasIntoModules || !s.uri)) const unique = list => list.map((item, i) => Object.assign(item, { index: i })).filter( (item, i, list) => !(list.find(x => x.name === item.name) && list.find(x => x.name === item.name).index < item.index)) Array.from(new Set(['types'].concat(config.additionalSchemaTemplates))).filter(dir => dir).forEach(dir => { state.typeTemplateDir = dir - const schemasArray = unique(_generateSchemas(server).concat(_generateSchemas(client))) - + const schemasArray = unique(generateSchemas(server, templates, { baseUrl: '' }).concat(generateSchemas(client, templates, { baseUrl: '' }))) macros.schemas[dir] = getTemplate('/sections/schemas', templates).replace(/\$\{schema.list\}/g, schemasArray.map(s => s.body).filter(body => body).join('\n')) macros.types[dir] = getTemplate('/sections/types', templates).replace(/\$\{schema.list\}/g, schemasArray.filter(x => !x.enum).map(s => s.body).filter(body => body).join('\n')) macros.enums[dir] = getTemplate('/sections/enums', templates).replace(/\$\{schema.list\}/g, schemasArray.filter(x => x.enum).map(s => s.body).filter(body => body).join('\n')) @@ -570,7 +568,7 @@ const generateMacros = (server, client, templates, languages, options = {}) => { }) state.typeTemplateDir = 'types' - const imports = Object.fromEntries(Array.from(new Set(Object.keys(templates).filter(key => key.startsWith('/imports/')).map(key => key.split('.').pop()))).map(key => [key, generateImports(server, templates, { destination: key })])) + const imports = Object.fromEntries(Array.from(new Set(Object.keys(templates).filter(key => key.startsWith('/imports/')).map(key => key.split('.').pop()))).map(key => [key, generateImports(server, client, templates, { destination: key })])) const initialization = generateInitialization(server, client, templates) const eventsEnum = generateEvents(server, templates) @@ -766,7 +764,9 @@ const insertMacros = (fContents = '', macros = {}) => { const examples = [...fContents.matchAll(/0 \/\* \$\{EXAMPLE\:(.*?)\} \*\//g)] examples.forEach((match) => { - fContents = fContents.replace(match[0], JSON.stringify(macros.examples[match[1]][0].value)) + // grab the examples, and check `onFoo` if `foo` isn't found... + const values = macros.examples[match[1]] || macros.examples['on' + match[1].charAt(0).toUpperCase() + match[1].substring(1)] + fContents = fContents.replace(match[0], JSON.stringify(values[0].value)) }) fContents = insertTableofContents(fContents) @@ -947,7 +947,7 @@ const isEnum = x => { return schema.type && schema.type === 'string' && Array.isArray(schema.enum) && x.title } -function generateSchemas(server, client, templates, options) { +function generateSchemas(server, templates, options) { let results = [] if (!server) { @@ -1083,44 +1083,49 @@ function getTemplateFromDestination(destination, templateName, templates) { return template } -const generateImports = (json, templates, options = { destination: '' }) => { +const generateImports = (server, client, templates, options = { destination: '' }) => { let imports = '' - if (rpcMethodsOrEmptyArray(json).length) { + if (rpcMethodsOrEmptyArray(server).length) { imports += getTemplate('/imports/rpc', templates) } - if (eventsOrEmptyArray(json).length) { + if (eventsOrEmptyArray(server).length) { imports += getTemplate('/imports/event', templates) } - if (eventsOrEmptyArray(json).find(m => m.params.length > 1)) { + if (eventsOrEmptyArray(server).find(m => m.params.length > 1)) { imports += getTemplate('/imports/context-event', templates) } - if (providersOrEmptyArray(json).length) { - imports += getTemplate('/imports/provider', templates) + if (getProvidedInterfaces(server).length) { + if (client) { + imports += getTemplate('/imports/provider', templates) + } + else { + imports += getTemplate('/imports/event-based-provider', templates) + } } - if (props(json).length) { + if (props(server).length) { imports += getTemplate('/imports/property', templates) } - if (temporalSets(json).length) { + if (temporalSets(server).length) { imports += getTemplate('/imports/temporal-set', templates) } - if (methodsWithXMethodsInResult(json).length) { + if (methodsWithXMethodsInResult(server).length) { imports += getTemplate('/imports/x-method', templates) } - if (callsMetrics(json).length) { + if (callsMetrics(server).length) { imports += getTemplateFromDestination(options.destination, '/imports/calls-metrics', templates) } let template = getTemplateFromDestination(options.destination, '/imports/default', templates) - const subschemas = getAllValuesForName("$id", json) - const subschemaLocation = json.definitions || json.components && json.components.schemas || {} + const subschemas = getAllValuesForName("$id", server) + const subschemaLocation = server.definitions || server.components && server.components.schemas || {} subschemas.shift() // remove main $id if (subschemas.length) { imports += subschemas.map(id => subschemaLocation[id].title).map(shared => template.replace(/\$\{info.title.lowercase\}/g, shared.toLowerCase())).join('') @@ -1133,7 +1138,7 @@ const generateImports = (json, templates, options = { destination: '' }) => { return imports } -const generateInitialization = (server, client, templates) => generateEventInitialization(server, client, templates) + '\n' + generateProviderInitialization(server, client, templates) + '\n' + generateDeprecatedInitialization(server, client, templates) +const generateInitialization = (server, client, templates) => generateEventInitialization(server, client, templates) + '\n' + generateProviderInitialization(client || server, templates) + '\n' + generateDeprecatedInitialization(server, client, templates) const generateEventInitialization = (server, client, templates) => { @@ -1150,23 +1155,43 @@ const generateEventInitialization = (server, client, templates) => { const getProviderInterfaceNameFromRPC = name => name.charAt(9).toLowerCase() + name.substr(10) // Drop onRequest prefix // TODO: this passes a JSON object to the template... might be hard to get working in non JavaScript languages. -const generateProviderInitialization = (server, client, templates) => compose( - reduce((acc, capability, i, arr) => { - document = client || server - const methods = providersOrEmptyArray(document) - .filter(m => m.tags.find(t => t['x-provides'] === capability)) - .map(m => ({ - name: getProviderInterfaceNameFromRPC(m.name), - focus: ((m.tags.find(t => t['x-allow-focus']) || { 'x-allow-focus': false })['x-allow-focus']), - response: ((m.tags.find(t => t['x-response']) || { 'x-response': null })['x-response']) !== null, - parameters: !providerHasNoParameters(localizeDependencies(getPayloadFromEvent(m), document)) - })) - return acc + getTemplate('/initializations/provider', templates) - .replace(/\$\{capability\}/g, capability) - .replace(/\$\{interface\}/g, JSON.stringify(methods)) - }, ''), - providedCapabilitiesOrEmptyArray -)(server) +const generateProviderInitialization = (document, templates) => { + let result = '' + const interfaces = getProvidedInterfaces(document) + + interfaces.forEach(_interface => { + const methods = getProviderInterface(_interface, document) + const capability = provides(methods[0]) + methods.forEach(method => { + result += getTemplate('/initializations/provider', templates) + .replace(/\$\{capability\}/g, capability) + .replace(/\$\{interface\}/g, _interface) + .replace(/\$\{method\.name\}/g, name(method)) + .replace(/\$\{method\.params\.array\}/g, JSON.stringify(method.params.map(p => p.name))) + .replace(/\$\{method\.focusable\}/g, ((method.tags.find(t => t['x-allow-focus']) || { 'x-allow-focus': false })['x-allow-focus'])) + .replace(/\$\{method\.response\}/g, !!method.result) + }) + }) + + return result +} +// compose( +// reduce((acc, capability, i, arr) => { +// document = client || server +// const methods = providersOrEmptyArray(document) +// .filter(m => m.tags.find(t => t['x-provides'] === capability)) +// .map(m => ({ +// name: getProviderInterfaceNameFromRPC(m.name), +// focus: ((m.tags.find(t => t['x-allow-focus']) || { 'x-allow-focus': false })['x-allow-focus']), +// response: ((m.tags.find(t => t['x-response']) || { 'x-response': null })['x-response']) !== null, +// parameters: !providerHasNoParameters(localizeDependencies(getPayloadFromEvent(m), document)) +// })) +// return acc + getTemplate('/initializations/provider', templates) +// .replace(/\$\{capability\}/g, capability) +// .replace(/\$\{interface\}/g, JSON.stringify(methods)) +// }, ''), +// providedCapabilitiesOrEmptyArray +// )(server) const generateDeprecatedInitialization = (server, client, templates) => { return compose( @@ -1308,6 +1333,8 @@ function generateMethods(server = {}, client = null, examples = {}, templates = // TODO: this is called too many places... let's reduce that to just generateMethods function insertMethodMacros(template, methodObj, server, client, templates, type = '', examples = {}) { try { + // need a guaranteed place to get client stuff from... + const document = client || server const moduleName = getModuleName(server) const info = { @@ -1357,18 +1384,26 @@ function insertMethodMacros(template, methodObj, server, client, templates, type const event = isEventMethod(methodObj) ? JSON.parse(JSON.stringify(methodObj)) : '' if (event) { - // if this is unidirection, do some simlification + // if this is unidirection, do some simplification if (!client) { result.schema = JSON.parse(JSON.stringify(getPayloadFromEvent(methodObj))) event.result.schema = getPayloadFromEvent(event) } + else { + const notifier = getNotifier(methodObj, client) + event.result = notifier.params.slice(-1)[0] + } event.params = event.params.filter(p => p.name !== 'listen') } - const notifier = event && client ? getNotifier(methodObj, client) : event - const eventParams = event.params && event.params.length ? getTemplate('/sections/parameters', templates) + event.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, event, server)).join('') : '' - const eventParamsRows = event.params && event.params.length ? event.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, event, server)).join('') : '' + if (methodObj.name === 'Discovery.onPullEntityInfo') { + console.dir(methodObj) + console.dir(event) + } + + const eventParams = event.params && event.params.length ? getTemplate('/sections/parameters', templates) + event.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, event, document)).join('') : '' + const eventParamsRows = event.params && event.params.length ? event.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, event, document)).join('') : '' let itemName = '' let itemType = '' @@ -1384,7 +1419,9 @@ function insertMethodMacros(template, methodObj, server, client, templates, type const setterFor = methodObj.tags.find(t => t.name === 'setter') && methodObj.tags.find(t => t.name === 'setter')['x-setter-for'] || '' const pullsResult = (puller || pullsFor) ? localizeDependencies(pullsFor || methodObj, server).params[1].schema : null - const pullsParams = (puller || pullsFor) ? localizeDependencies(getPayloadFromEvent(puller || methodObj, client), server, null, { mergeAllOfs: true }).properties.parameters : null + + + const pullsParams = (puller || pullsFor) ? localizeDependencies(getPayloadFromEvent(puller || methodObj, document), document, null, { mergeAllOfs: true }).properties.parameters : null const pullsResultType = (pullsResult && (type === 'methods')) ? Types.getSchemaShape(pullsResult, server, { templateDir: state.typeTemplateDir, namespace: !config.copySchemasIntoModules }) : '' const pullsForType = pullsResult && Types.getSchemaType(pullsResult, server, { templateDir: state.typeTemplateDir, namespace: !config.copySchemasIntoModules }) @@ -1398,28 +1435,28 @@ function insertMethodMacros(template, methodObj, server, client, templates, type const serializedParams = (type === 'methods') ? flattenedMethod.params.map(param => Types.getSchemaShape(param.schema, server, { templateDir: 'parameter-serialization', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })).join('\n') : '' const resultInst = result && (type === 'methods') ? Types.getSchemaShape(flattenedMethod.result.schema, server, { templateDir: 'result-instantiation', property: flattenedMethod.result.name, required: flattenedMethod.result.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' // w/out primitive: true, getSchemaShape skips anonymous types, like primitives const resultInit = result && (type === 'methods') ? Types.getSchemaShape(flattenedMethod.result.schema, server, { templateDir: 'result-initialization', property: flattenedMethod.result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' // w/out primitive: true, getSchemaShape skips anonymous types, like primitives - const serializedEventParams = event && (type === 'methods') ? flattenedMethod.params.filter(p => p.name !== 'listen').map(param => Types.getSchemaShape(param.schema, server, {templateDir: 'parameter-serialization', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })).join('\n') : '' + const serializedEventParams = event && (type === 'methods') ? flattenedMethod.params.filter(p => p.name !== 'listen').map(param => Types.getSchemaShape(param.schema, document, {templateDir: 'parameter-serialization', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })).join('\n') : '' // this was wrong... check when we merge if it was fixed - const callbackSerializedList = event && (type === 'methods') ? Types.getSchemaShape(event.result.schema, server, { templateDir: eventHasOptionalParam(event) && !event.tags.find(t => t.name === 'provider') ? 'callback-serialization' : 'callback-result-serialization', property: result.name, required: event.result.schema.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' - const callbackInitialization = event && (type === 'methods') ? (eventHasOptionalParam(event) && !event.tags.find(t => t.name === 'provider') ? (event.params.map(param => isOptionalParam(param) ? Types.getSchemaShape(param.schema, server, { templateDir: 'callback-initialization-optional', property: param.name, required: param.required, primitive: true, skipTitleOnce: true }) : '').filter(param => param).join('\n') + '\n') : '' ) + (Types.getSchemaShape(event.result.schema, server, { templateDir: 'callback-initialization', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })) : '' + const callbackSerializedList = event && (type === 'methods') ? Types.getSchemaShape(event.result.schema, document, { templateDir: eventHasOptionalParam(event) && !event.tags.find(t => t.name === 'provider') ? 'callback-serialization' : 'callback-result-serialization', property: result.name, required: event.result.schema.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' + const callbackInitialization = event && (type === 'methods') ? (eventHasOptionalParam(event) && !event.tags.find(t => t.name === 'provider') ? (event.params.map(param => isOptionalParam(param) ? Types.getSchemaShape(param.schema, document, { templateDir: 'callback-initialization-optional', property: param.name, required: param.required, primitive: true, skipTitleOnce: true }) : '').filter(param => param).join('\n') + '\n') : '' ) + (Types.getSchemaShape(event.result.schema, document, { templateDir: 'callback-initialization', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })) : '' let callbackInstantiation = '' if (event) { if (eventHasOptionalParam(event) && !event.tags.find(t => t.name === 'provider')) { - callbackInstantiation = (type === 'methods') ? Types.getSchemaShape(event.result.schema, server, { templateDir: 'callback-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' - let paramInstantiation = (type === 'methods') ? event.params.map(param => isOptionalParam(param) ? Types.getSchemaShape(param.schema, server, { templateDir: 'callback-context-instantiation', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '').filter(param => param).join('\n') : '' - let resultInitialization = (type === 'methods') ? Types.getSchemaShape(event.result.schema, server, { templateDir: 'callback-value-initialization', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' - let resultInstantiation = (type === 'methods') ? Types.getSchemaShape(event.result.schema, server, { templateDir: 'callback-value-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' + callbackInstantiation = (type === 'methods') ? Types.getSchemaShape(event.result.schema, document, { templateDir: 'callback-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' + let paramInstantiation = (type === 'methods') ? event.params.map(param => isOptionalParam(param) ? Types.getSchemaShape(param.schema, document, { templateDir: 'callback-context-instantiation', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '').filter(param => param).join('\n') : '' + let resultInitialization = (type === 'methods') ? Types.getSchemaShape(event.result.schema, document, { templateDir: 'callback-value-initialization', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' + let resultInstantiation = (type === 'methods') ? Types.getSchemaShape(event.result.schema, document, { templateDir: 'callback-value-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' callbackInstantiation = callbackInstantiation .replace(/\$\{callback\.param\.instantiation\.with\.indent\}/g, indent(paramInstantiation, ' ', 3)) .replace(/\$\{callback\.result\.initialization\.with\.indent\}/g, indent(resultInitialization, ' ', 1)) .replace(/\$\{callback\.result\.instantiation\}/g, resultInstantiation) } else { - callbackInstantiation = (type === 'methods') ? Types.getSchemaShape(event.result.schema, server, { templateDir: 'callback-result-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' + callbackInstantiation = (type === 'methods') ? Types.getSchemaShape(event.result.schema, document, { templateDir: 'callback-result-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '' } } // hmm... how is this different from callbackSerializedList? i guess they get merged? - const callbackResponseInst = event && (type === 'methods') ? (eventHasOptionalParam(event) ? (event.params.map(param => isOptionalParam(param) ? Types.getSchemaShape(param.schema, server, { templateDir: 'callback-response-instantiation', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '').filter(param => param).join(', ') + ', ') : '' ) + (Types.getSchemaShape(event.result.schema, server, { templateDir: 'callback-response-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })) : '' + const callbackResponseInst = event && (type === 'methods') ? (eventHasOptionalParam(event) ? (event.params.map(param => isOptionalParam(param) ? Types.getSchemaShape(param.schema, document, { templateDir: 'callback-response-instantiation', property: param.name, required: param.required, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules }) : '').filter(param => param).join(', ') + ', ') : '' ) + (Types.getSchemaShape(event.result.schema, document, { templateDir: 'callback-response-instantiation', property: result.name, primitive: true, skipTitleOnce: true, namespace: !config.copySchemasIntoModules })) : '' const resultType = result && result.schema ? Types.getSchemaType(result.schema, server, { templateDir: state.typeTemplateDir, namespace: !config.copySchemasIntoModules }) : '' const resultSchemaType = result && result.schema.type const resultJsonType = result && result.schema ? Types.getSchemaType(result.schema, server, { templateDir: 'json-types', namespace: !config.copySchemasIntoModules }) : '' @@ -1433,13 +1470,13 @@ function insertMethodMacros(template, methodObj, server, client, templates, type const resultParams = result && generateResultParams(result.schema, server, templates, { name: result.name}) // todo: what does prefix do in Types.mjs? need to account for it somehow - const callbackResultJsonType = event && result.schema ? Types.getSchemaType(result.schema, server, { templateDir: 'json-types', namespace: !config.copySchemasIntoModules }) : '' + const callbackResultJsonType = event && result.schema ? Types.getSchemaType(result.schema, document, { templateDir: 'json-types', namespace: !config.copySchemasIntoModules }) : '' const pullsForParamType = pullsParams ? Types.getSchemaType(pullsParams, server, { namespace: !config.copySchemasIntoModules }) : '' const pullsForJsonType = pullsResult ? Types.getSchemaType(pullsResult, server, { templateDir: 'json-types', namespace: !config.copySchemasIntoModules }) : '' const pullsForParamJsonType = pullsParams ? Types.getSchemaType(pullsParams, server, { templateDir: 'json-types', namespace: !config.copySchemasIntoModules }) : '' - const pullsEventParamName = event ? Types.getSchemaInstantiation(event.result, server, event.name, { instantiationType: 'pull.param.name', namespace: !config.copySchemasIntoModules }) : '' + const pullsEventParamName = event ? Types.getSchemaInstantiation(event.result, document, event.name, { instantiationType: 'pull.param.name', namespace: !config.copySchemasIntoModules }) : '' let seeAlso = '' if (isPolymorphicPullMethod(methodObj) && pullsForType) { @@ -1492,13 +1529,10 @@ function insertMethodMacros(template, methodObj, server, client, templates, type .replace(/\$\{event\.name\}/g, method.name.toLowerCase()[2] + method.name.substr(3)) .replace(/\$\{event\.params\}/g, eventParams) .replace(/\$\{event\.params\.table\.rows\}/g, eventParamsRows) - .replace(/\$\{notifier\.name\}/g, notifier?.name) - .replace(/\$\{notifier\.value\.type\}/g, notifier ? Types.getSchemaType(notifier.params.slice(-1)[0].schema, client || server, { templateDir: state.typeTemplateDir, title: true, asPath: false, result: true, namespace: !config.copySchemasIntoModules }) : '') - .replace(/\$\{notifier\.value\.json\}/g, notifier ? Types.getSchemaType(notifier.params.slice(-1)[0].schema, client || server, { templateDir: 'json-types', title: true, code: false, link: false, asPath: false, expandEnums: false, namespace: !config.copySchemasIntoModules }) : '') .replace(/\$\{if\.event\.params\}(.*?)\$\{end\.if\.event\.params\}/gms, event && event.params.length ? '$1' : '') .replace(/\$\{if\.event\.callback\.params\}(.*?)\$\{end\.if\.event\.callback\.params\}/gms, event && eventHasOptionalParam(event) ? '$1' : '') - .replace(/\$\{event\.signature\.params\}/g, event ? Types.getMethodSignatureParams(event, server, { namespace: !config.copySchemasIntoModules }) : '') - .replace(/\$\{event\.signature\.callback\.params\}/g, event ? Types.getMethodSignatureParams(event, server, { callback: true, namespace: !config.copySchemasIntoModules }) : '') + .replace(/\$\{event\.signature\.params\}/g, event ? Types.getMethodSignatureParams(event, document, { namespace: !config.copySchemasIntoModules }) : '') + .replace(/\$\{event\.signature\.callback\.params\}/g, event ? Types.getMethodSignatureParams(event, document, { callback: true, namespace: !config.copySchemasIntoModules }) : '') .replace(/\$\{event\.params\.serialization\}/g, serializedEventParams) .replace(/\$\{event\.callback\.serialization\}/g, callbackSerializedList) .replace(/\$\{event\.callback\.initialization\}/g, callbackInitialization) @@ -1522,8 +1556,7 @@ function insertMethodMacros(template, methodObj, server, client, templates, type .replace(/\$\{method\.result\.link\}/g, getLinkForSchema(result.schema, server)) //, baseUrl: options.baseUrl .replace(/\$\{method\.result\.type\}/g, Types.getSchemaType(result.schema, server, { templateDir: state.typeTemplateDir, title: true, asPath: false, result: true, namespace: !config.copySchemasIntoModules })) //, baseUrl: options.baseUrl .replace(/\$\{method\.result\.json\}/g, Types.getSchemaType(result.schema, server, { templateDir: 'json-types', title: true, code: false, link: false, asPath: false, expandEnums: false, namespace: !config.copySchemasIntoModules })) - // todo: what does prefix do? - .replace(/\$\{event\.result\.type\}/g, isEventMethod(methodObj) ? Types.getMethodSignatureResult(event, server, { callback: true, namespace: !config.copySchemasIntoModules }) : '') + .replace(/\$\{event\.result\.type\}/g, isEventMethod(methodObj) ? Types.getSchemaType(event.result.schema, document, { templateDir: state.typeTemplateDir, title: true, asPath: false, result: true, namespace: !config.copySchemasIntoModules }) : '') .replace(/\$\{event\.result\.json\.type\}/g, resultJsonType) .replace(/\$\{event\.result\.json\.type\}/g, callbackResultJsonType) .replace(/\$\{event\.pulls\.param\.name\}/g, pullsEventParamName) @@ -1761,11 +1794,20 @@ function generateResultParams(result, json, templates, { name = '' } = {}) { } function insertSchemaMacros(template, title, schema, document) { - return template.replace(/\$\{property\}/g, title) + try { + return template.replace(/\$\{property\}/g, title) .replace(/\$\{type\}/g, Types.getSchemaType(schema, document, { templateDir: state.typeTemplateDir, code: false, namespace: !config.copySchemasIntoModules })) .replace(/\$\{type.link\}/g, getLinkForSchema(schema, document)) .replace(/\$\{description\}/g, schema.description || '') .replace(/\$\{name\}/g, title || '') + } + catch(error) { + console.log(`Error processing method ${schema.title}`) + console.dir(schema) + console.log() + console.dir(error) + process.exit(1) + } } function insertParameterMacros(template, param, method, document) { @@ -1836,27 +1878,29 @@ function generateProviderSubscribe(server, client, templates, bidirectional) { } function generateProviderInterfaces(server, client, templates, codeblock, directory, bidirectional) { - const interfaces = getProvidedCapabilities(server) + const interfaces = getProvidedInterfaces(client || server) + console.dir(interfaces) + let template = getTemplate('/sections/provider-interfaces', templates) - const providers = reduce((acc, capability) => { + const providers = reduce((acc, _interface) => { let providerTemplate = getTemplate('/codeblocks/provider', templates) - const template = insertProviderInterfaceMacros(providerTemplate, capability, server, client, codeblock, directory, templates, bidirectional) + const template = insertProviderInterfaceMacros(providerTemplate, _interface, server, client, codeblock, directory, templates, bidirectional) return acc + template }, '', interfaces) return interfaces.length ? template.replace(/\$\{providers\.list\}/g, providers) : '' } -function getProviderInterfaceName(iface, capability, document = {}) { +function getProviderInterfaceName(iface, _interface, document = {}) { const [ module, method ] = iface[0].name.split('.') - const uglyName = capability.split(":").slice(-2).map(capitalize).reverse().join('') + "Provider" + const uglyName = _interface.split(":").slice(-2).map(capitalize).reverse().join('') + "Provider" let name = iface.length === 1 ? method.charAt(0).toUpperCase() + method.substr(1) + "Provider" : uglyName if (document.info['x-interface-names']) { - name = document.info['x-interface-names'][capability] || name + name = document.info['x-interface-names'][_interface] || name } return name } @@ -1901,15 +1945,15 @@ function insertProviderSubscribeMacros(template, capability, server = {}, client // TODO: split into /codeblocks/class & /codeblocks/interface (and /classes/* & /interaces/*) // TODO: ideally this method should be configurable with tag-names/template-names -function insertProviderInterfaceMacros(template, capability, server = {}, client = null, codeblock='interface', directory='interfaces', templates, bidirectional) { +function insertProviderInterfaceMacros(template, _interface, server = {}, client = null, codeblock='interface', directory='interfaces', templates, bidirectional) { const document = client || server - const iface = getProviderInterface(capability, document, bidirectional) - let name = getProviderInterfaceName(iface, capability, document) + const iface = getProviderInterface(_interface, document, bidirectional) + let name = _interface //getProviderInterfaceName(iface, _interface, document) let xValues let interfaceShape = getTemplate(`/codeblocks/${codeblock}`, templates) interfaceShape = interfaceShape.replace(/\$\{name\}/g, name) - .replace(/\$\{capability\}/g, capability) + .replace(/\$\{capability\}/g, _interface) .replace(/[ \t]*\$\{methods\}[ \t]*\n/g, iface.map(method => { const focusable = method.tags.find(t => t['x-allow-focus']) const interfaceTemplate = `/${directory}/` + (focusable ? 'focusable' : 'default') @@ -2004,7 +2048,7 @@ function insertProviderInterfaceMacros(template, capability, server = {}, client template = template.replace(/\$\{provider\}/g, name) template = template.replace(/\$\{interface\}/g, interfaceShape) - template = template.replace(/\$\{capability\}/g, capability) + template = template.replace(/\$\{capability\}/g, _interface) template = insertProviderXValues(template, document, xValues) return template diff --git a/src/macrofier/index.mjs b/src/macrofier/index.mjs index fca766c1..abb3f5f6 100644 --- a/src/macrofier/index.mjs +++ b/src/macrofier/index.mjs @@ -21,7 +21,7 @@ import { emptyDir, readDir, readFiles, readFilesPermissions, readJson, writeFiles, writeFilesPermissions, writeText } from '../shared/filesystem.mjs' import { getTemplate, getTemplateForModule } from '../shared/template.mjs' -import { getModule, hasPublicAPIs } from '../shared/modules.mjs' +import { getClientModule, getModule, hasPublicAPIs } from '../shared/modules.mjs' import { logHeader, logSuccess } from '../shared/io.mjs' import Types from './types.mjs' import path from 'path' @@ -129,12 +129,15 @@ const macrofy = async ( const staticModules = staticModuleNames.map(name => ( { info: { title: name } } )) let modules + const time = Date.now() if (hidePrivate) { modules = moduleList.map(name => getModule(name, serverRpc, copySchemasIntoModules, extractSubSchemas)).filter(hasPublicAPIs) } else { modules = moduleList.map(name => getModule(name, serverRpc, copySchemasIntoModules, extractSubSchemas)) } + logSuccess(`Separated modules (${Date.now() - time}ms)`) + // Grab all schema groups w/ a URI string. These came from some external json-schema that was bundled into the OpenRPC const externalSchemas = {} @@ -191,8 +194,10 @@ const macrofy = async ( let append = false modules.forEach(module => { + console.log(`Generating ${module.info.title}...`) start = Date.now() - const macros = engine.generateMacros(module, clientRpc, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, createPolymorphicMethods: createPolymorphicMethods, type: 'methods'}) + const clientRpc2 = clientRpc && getClientModule(module.info.title, clientRpc, module) + const macros = engine.generateMacros(module, clientRpc2, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, createPolymorphicMethods: createPolymorphicMethods, type: 'methods'}) logSuccess(`Generated macros for module ${module.info.title} (${Date.now() - start}ms)`) // Pick the index and defaults templates for each module. diff --git a/src/openrpc/index.mjs b/src/openrpc/index.mjs index 22043b93..94f2de58 100644 --- a/src/openrpc/index.mjs +++ b/src/openrpc/index.mjs @@ -62,7 +62,7 @@ const run = async ({ const markdown = await readFiles(descriptionsList, path.join(input, 'descriptions')) const isNotifier = method => method.tags.find(t => t.name === 'notifier') - const isProvider = method => method.tags.find(t => t.name === 'capabilities')['x-provides'] && !method.tags.find(t => t.name === 'event') && !method.tags.find(t => t.name === 'polymorphic-pull') + const isProvider = method => method.tags.find(t => t.name === 'capabilities')['x-provides'] && !method.tags.find(t => t.name === 'event') && !method.tags.find(t => t.name === 'polymorphic-pull') && !method.tags.find(t => t.name === 'registration') const isClientAPI = method => client && (isNotifier(method) || isProvider(method)) const isServerAPI = method => !isClientAPI(method) diff --git a/src/shared/json-schema.mjs b/src/shared/json-schema.mjs index e010127f..46873ebb 100644 --- a/src/shared/json-schema.mjs +++ b/src/shared/json-schema.mjs @@ -168,6 +168,7 @@ const namespaceRefs = (uri, namespace, schema) => { if (schema.hasOwnProperty('$ref') && (typeof schema['$ref'] === 'string')) { const parts = schema.$ref.split('#') if (parts[0] === uri && parts[1].indexOf('.') === -1) { + const old = schema.$ref schema['$ref'] = schema['$ref'].split('#').map( x => x === uri ? uri : x.split('/').map((y, i, arr) => i===arr.length-1 ? namespace + '.' + y : y).join('/')).join('#') } } diff --git a/src/shared/methods.mjs b/src/shared/methods.mjs index a09524b5..0e2a6067 100644 --- a/src/shared/methods.mjs +++ b/src/shared/methods.mjs @@ -17,14 +17,21 @@ */ const tag = (method, name) => method.tags.find(tag => tag.name === name) +const extension = (method, name) => (method.tags.find(t => t[name]) || {})[name] + export const capabilities = method => tag(method, 'capabilities') export const isProvider = method => capabilities(method)['x-provides'] export const isPusher = method => capabilities(method)['x-push'] export const isNotifier = method => method.tags.find(t => t.name === 'notifier') export const isEvent = method => tag(method, 'event') +export const isRegistration = method => !tag(method, 'registration') +export const isProviderInterface = method => isProvider(method) && !isRegistration(method) && !isPusher(method) export const name = method => method.name.split('.').pop() export const rename = (method, renamer) => method.name.split('.').map((x, i, arr) => i === (arr.length-1) ? renamer(x) : x).join('.') export const getNotifier = (method, client) => client.methods.find(m => m.name === method.tags.find(t => t.name === "event")['x-notifier']) -export const getEvent = (method, server) => server.methods.find(m => m.name === method.tags.find(t => t.name === "notifier")['x-event']) \ No newline at end of file +export const getEvent = (method, server) => server.methods.find(m => m.name === method.tags.find(t => t.name === "notifier")['x-event']) + +export const provides = (method) => extension(method, 'x-provides') + diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index 9c26b672..95248cc1 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -31,7 +31,7 @@ import predicates from 'crocks/predicates/index.js' import { getExternalSchemaPaths, isDefinitionReferencedBySchema, isNull, localizeDependencies, isSchema, getLocalSchemaPaths, replaceRef, getLinkedSchemaUris, getAllValuesForName, replaceUri } from './json-schema.mjs' import { getReferencedSchema } from './json-schema.mjs' const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates -import { getNotifier, name as methodName, rename as methodRename } from './methods.mjs' +import { getNotifier, name as methodName, rename as methodRename, provides } from './methods.mjs' // util for visually debugging crocks ADTs const inspector = obj => { @@ -93,17 +93,27 @@ const isProviderInterfaceMethod = method => { const getProvidedCapabilities = (json) => { return Array.from(new Set([...getMethods(json).filter(isProviderInterfaceMethod).map(method => method.tags.find(tag => tag['x-provides'])['x-provides'])])) } +const getProvidedInterfaces = (json) => { + return Array.from(new Set(json.methods.filter(m => m.tags.find(t => t['x-provides'])) + .filter(m => !m.tags.find(t => t.name.startsWith('polymorphic-pull'))) + .map(m => m.name.split('.')[0]))) +} + +// TODO: this code is all based on capability, but we now support two interfaces in the same capability. need to refactor -const getProviderInterfaceMethods = (capability, json, prefix) => { - return getMethods(json).filter(method => methodName(method).startsWith(prefix) && method.tags && method.tags.find(tag => tag['x-provides'] === capability)) +const getProviderInterfaceMethods = (_interface, json, prefix) => { + return json.methods.filter(method => method.name.split('.')[0] === _interface).filter(isProviderInterfaceMethod) + //return getMethods(json).filter(method => methodName(method).startsWith(prefix) && method.tags && method.tags.find(tag => tag['x-provides'] === _interface)) } -function getProviderInterface(capability, module, bidirectional = false) { +function getProviderInterface(_interface, module) { module = JSON.parse(JSON.stringify(module)) - const iface = getProviderInterfaceMethods(capability, module, bidirectional ? '' : 'onRequest').map(method => localizeDependencies(method, module, null, { mergeAllOfs: true })) - if (iface.every(method => methodName(method).startsWith('onRequest'))) { - console.log(`Transforming legacy provider interface ${capability}`) + // TODO: localizeDependencies?? + const iface = getProviderInterfaceMethods(_interface, module).map(method => localizeDependencies(method, module, null, { mergeAllOfs: true })) + + if (iface.length && iface.every(method => methodName(method).startsWith('onRequest'))) { + console.log(`Transforming legacy provider interface ${_interface}`) updateUnidirectionalProviderInterface(iface, module) } @@ -335,12 +345,11 @@ const getPayloadFromEvent = (event, client) => { const choice = choices.find(schema => schema.title !== 'ListenResponse' && !(schema['$ref'] || '').endsWith('/ListenResponse')) return choice } - else { - return getNotifier(event, client).params.slice(-1)[0].schema + else if (client) { + const payload = getNotifier(event, client).params.slice(-1)[0].schema + return payload } - } else { - return notifier.params[notifier.params.length-1].schema - } + } } catch (error) { console.dir(event) @@ -469,7 +478,7 @@ const createPullEventFromPush = (pusher, json) => { name: 'polymorphic-pull-event' }) - const requestType = methodRename(pusher, name => name.charAt(0).toUpperCase() + name.substr(1) + "FederatedRequest").split('.').pop() + const requestType = methodRename(pusher, name => name.charAt(0).toUpperCase() + name.substr(1) + "FederatedRequest") event.params.push({ name: "request", summary: "A " + requestType + " object.", @@ -1011,6 +1020,93 @@ const generateEventSubscribers = json => { return json } +const generateProviderRegistrars = json => { + const interfaces = getProvidedInterfaces(json) + + interfaces.forEach(name => { + json.methods.push({ + name: name + ".provide", + tags: [ + { + "name": "registration", + "x-interface": name + }, + { + "name": "capabilities", + "x-provides": json.methods.find(m => m.name.startsWith(name) && m.tags.find(t => t.name === 'capabilities')['x-provides']).tags.find(t => t.name === 'capabilities')['x-provides'] + } + + ], + params: [ + { + name: "enabled", + schema: { + type: "boolean" + } + } + ], + result: { + name: "result", + schema: { + type: "null" + } + }, + examples: [ + { + name: "Default example", + params: [ + { + name: "enabled", + value: true + } + ], + result: { + name: "result", + value: null + } + } + ] + }) + }) + + console.dir(interfaces) + + return json + const notifiers = json.methods.filter( m => m.tags && m.tags.find(t => t.name == 'notifier')) || [] + + notifiers.forEach(notifier => { + const tag = notifier.tags.find(tag => tag.name === 'notifier') + // if there's an x-event extension, this denotes an editorially created subscriber + if (!tag['x-event']) { + tag['x-event'] = methodRename(notifier, name => 'on' + name.charAt(0).toUpperCase() +! name.substring(1)) + } + const subscriber = json.methods.find(method => method.name === tag['x-event']) + + if (!subscriber) { + const subscriber = JSON.parse(JSON.stringify(notifier)) + subscriber.name = methodRename(subscriber, name => 'on' + name.charAt(0).toUpperCase() + name.substring(1)) + subscriber.params.pop() + subscriber.params.push({ + name: 'listen', + schema: { + type: 'boolean' + } + }) + subscriber.tags.find(t => t.name === 'notifier')['x-notifier'] = notifier.name + subscriber.tags.find(t => t.name === 'notifier').name = 'event' + subscriber.result = { + name: "result", + schema: { + "type": "null" + } + } + json.methods.push(subscriber) + } + }) + + return json +} + const generateUnidirectionalEventMethods = json => { const events = json.methods.filter( m => m.tags && m.tags.find(t => t.name == 'notifier')) || [] @@ -1291,6 +1387,7 @@ const fireboltize = (json, bidirectional) => { if (bidirectional) { console.log('Creating bidirectional APIs') json = generateEventSubscribers(json) + json = generateProviderRegistrars(json) // generateInterfaceProviders } else { @@ -1558,6 +1655,29 @@ const getModule = (name, json, copySchemas, extractSubSchemas) => { return removeUnusedSchemas(openrpc) } +const getClientModule = (name, client, server) => { + + const notifierFor = m => (m.tags.find(t => t['x-event']) || {})['x-event'] + const interfaces = server.methods.filter(m => m.tags.find(t => t['x-interface'])) + .map(m => m.tags.find(t => t['x-interface'])['x-interface']) + //const + console.dir(interfaces) + + let openrpc = JSON.parse(JSON.stringify(client)) + openrpc.methods = openrpc.methods + .filter(method => notifierFor(method) && notifierFor(method).startsWith(name + '.') || interfaces.find(name => method.name.startsWith(name + '.'))) + openrpc.info.title = name + openrpc.components.schemas = Object.fromEntries(Object.entries(openrpc.components.schemas).filter( ([key, schema]) => key.startsWith('http') || key.split('.')[0] === name)) + console.dir(Object.keys(openrpc.components.schemas)) + if (client.info['x-module-descriptions'] && client.info['x-module-descriptions'][name]) { + openrpc.info.description = client.info['x-module-descriptions'][name] + } + delete openrpc.info['x-module-descriptions'] + + openrpc = promoteAndNameXSchemas(openrpc) + return removeUnusedBundles(removeUnusedSchemas(openrpc)) +} + const getSemanticVersion = json => { const str = json && json.info && json.info.version || '0.0.0-unknown.0' const version = { @@ -1619,6 +1739,7 @@ export { getMethods, getProviderInterface, getProvidedCapabilities, + getProvidedInterfaces, getSetterFor, getSubscriberFor, getEnums, @@ -1634,6 +1755,7 @@ export { removeUnusedSchemas, removeUnusedBundles, getModule, + getClientModule, getSemanticVersion, addExternalMarkdown, addExternalSchemas,