From 10fd09eb37454711126f245f29184d7bef0cfa42 Mon Sep 17 00:00:00 2001 From: ruoyunbai <1153712410@qq.com> Date: Mon, 3 Nov 2025 23:10:11 +0800 Subject: [PATCH] flow --- .node-red-data/projects/zsy/flows.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-red-data/projects/zsy/flows.json b/.node-red-data/projects/zsy/flows.json index e155c7e..ec8f6f6 100644 --- a/.node-red-data/projects/zsy/flows.json +++ b/.node-red-data/projects/zsy/flows.json @@ -2090,7 +2090,7 @@ "type": "function", "z": "78d15f59dee4b6d8", "name": "dms转化为oas", - "func": "'use strict';\n\nconst DEFAULT_OPENAPI_VERSION = '3.0.1';\nconst DEFAULT_API_VERSION = '1.0.0';\nconst FALLBACK_BASE_URL = 'https://www.dev.ideas.cnpc/api/dms/well_kd_wellbore_ideas01/v1';\nconst FALLBACK_HEADERS = {\n 'Content-Type': 'application/json',\n 'Authorization': '1',\n 'Dataregion': 'ZZLH',\n};\n\nlet schema;\ntry {\n // 优先使用 HTTP In 提供的 req.body.schema,缺省时回退到 msg.payload。\n const schemaInput = extractSchemaInput(msg);\n schema = parseSchema(schemaInput);\n} catch (error) {\n node.error(`DMS -> Swagger 解析失败:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n}\n\nconst resourceTitle = typeof schema.title === 'string' && schema.title.trim()\n ? schema.title.trim()\n : 'DMS Resource';\n\nconst resourceName = pascalCase(resourceTitle);\nconst collectionName = pluralize(kebabCase(resourceTitle));\nconst identityField = Array.isArray(schema.identityId) && schema.identityId.length > 0\n ? String(schema.identityId[0])\n : 'id';\n\nconst schemaComponent = buildComponentSchema(resourceName, schema);\nconst identitySchema = schemaComponent.properties && schemaComponent.properties[identityField]\n ? clone(schemaComponent.properties[identityField])\n : { type: 'string' };\n\nconst openApiDocument = {\n openapi: DEFAULT_OPENAPI_VERSION,\n info: {\n title: `${resourceTitle} API`,\n version: DEFAULT_API_VERSION,\n description: schema.description || `${schema.$id || ''}`.trim(),\n 'x-dms-sourceId': schema.$id || undefined,\n },\n paths: buildCrudPaths({\n collectionName,\n resourceName,\n identityField,\n identitySchema,\n }),\n components: {\n schemas: {\n [resourceName]: schemaComponent,\n },\n },\n};\n\nconst crudConfig = Object.assign({}, msg.crudConfig || {});\n\nif (!crudConfig.baseUrl) {\n crudConfig.baseUrl = FALLBACK_BASE_URL;\n}\n\nconst headers = Object.assign({}, FALLBACK_HEADERS, crudConfig.headers || {});\nconst dataRegionValue = extractDataRegion(schema, headers.Dataregion);\n\nif (dataRegionValue) {\n headers.Dataregion = dataRegionValue;\n crudConfig.dataRegion = dataRegionValue;\n}\n\ncrudConfig.headers = headers;\ncrudConfig.identityField = crudConfig.identityField || identityField;\n\nmsg.crudConfig = crudConfig;\nmsg.headers = Object.assign({}, headers);\n\nmsg.oas_def = openApiDocument;\nmsg.payload = openApiDocument;\nreturn msg;\n\nfunction extractDataRegion(dmsSchema, fallback) {\n if (!dmsSchema || typeof dmsSchema !== 'object') {\n return fallback;\n }\n\n if (typeof dmsSchema.dataRegion === 'string' && dmsSchema.dataRegion.trim()) {\n return dmsSchema.dataRegion.trim();\n }\n\n if (dmsSchema.defaultShow && Array.isArray(dmsSchema.defaultShow)) {\n const candidate = dmsSchema.defaultShow.find(item => item && typeof item === 'string' && item.toLowerCase().includes('dataregion'));\n if (candidate) {\n return fallback;\n }\n }\n\n const props = dmsSchema.properties;\n if (props && typeof props === 'object' && props.dataRegion) {\n if (typeof props.dataRegion.default === 'string' && props.dataRegion.default.trim()) {\n return props.dataRegion.default.trim();\n }\n }\n\n return fallback;\n}\n\nfunction extractSchemaInput(message) {\n if (message && message.req && message.req.body && typeof message.req.body === 'object') {\n if (message.req.body.schema !== undefined) {\n return message.req.body.schema;\n }\n if (looksLikeDmsSchema(message.req.body)) {\n return message.req.body;\n }\n }\n\n if (message && message.payload !== undefined) {\n if (message.payload && typeof message.payload === 'object') {\n if (message.payload.schema !== undefined) {\n return message.payload.schema;\n }\n if (looksLikeDmsSchema(message.payload)) {\n return message.payload;\n }\n }\n return message.payload;\n }\n\n throw new Error('未找到schema,请在请求体的schema字段或msg.payload中提供');\n}\n\nfunction parseSchema(source) {\n if (typeof source === 'string') {\n try {\n return JSON.parse(source);\n } catch (error) {\n throw new Error(`JSON 解析失败:${error.message}`);\n }\n }\n\n if (!source || typeof source !== 'object') {\n throw new Error('schema 必须是 DMS 定义对象或 JSON 字符串');\n }\n\n return source;\n}\n\nfunction buildComponentSchema(resourceName, dmsSchema) {\n const { properties = {}, required = [], groupView, identityId, naturalKey, defaultShow } = dmsSchema;\n const openApiProps = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n openApiProps[propName] = mapProperty(propSchema);\n }\n\n return {\n type: 'object',\n required: Array.isArray(required) ? required.slice() : [],\n properties: openApiProps,\n description: dmsSchema.description || dmsSchema.title || resourceName,\n 'x-dms-groupView': groupView || undefined,\n 'x-dms-identityId': identityId || undefined,\n 'x-dms-naturalKey': naturalKey || undefined,\n 'x-dms-defaultShow': defaultShow || undefined,\n };\n}\n\nfunction mapProperty(propSchema) {\n if (!propSchema || typeof propSchema !== 'object') {\n return { type: 'string' };\n }\n\n const result = {};\n\n const type = normalizeType(propSchema.type);\n result.type = type.type;\n if (type.format) {\n result.format = type.format;\n }\n if (type.items) {\n result.items = type.items;\n }\n\n if (propSchema.description) {\n result.description = propSchema.description;\n } else if (propSchema.title) {\n result.description = propSchema.title;\n }\n\n if (propSchema.enum) {\n result.enum = propSchema.enum.slice();\n }\n\n if (propSchema.default !== undefined) {\n result.default = propSchema.default;\n }\n\n if (propSchema.mask) {\n result['x-dms-mask'] = propSchema.mask;\n }\n if (propSchema.geom) {\n result['x-dms-geom'] = propSchema.geom;\n }\n if (propSchema.title) {\n result['x-dms-title'] = propSchema.title;\n }\n if (propSchema.type) {\n result['x-dms-originalType'] = propSchema.type;\n }\n\n return result;\n}\n\nfunction normalizeType(typeValue) {\n const normalized = typeof typeValue === 'string' ? typeValue.toLowerCase() : undefined;\n\n switch (normalized) {\n case 'number':\n case 'integer':\n case 'long':\n case 'float':\n case 'double':\n return { type: 'number' };\n case 'boolean':\n return { type: 'boolean' };\n case 'array':\n return { type: 'array', items: { type: 'string' } };\n case 'date':\n return { type: 'string', format: 'date' };\n case 'date-time':\n return { type: 'string', format: 'date-time' };\n case 'object':\n return { type: 'object' };\n case 'string':\n default:\n return { type: 'string' };\n }\n}\n\nfunction buildCrudPaths({ collectionName, resourceName, identityField, identitySchema }) {\n const ref = `#/components/schemas/${resourceName}`;\n const collectionPath = `/${collectionName}`;\n const itemPath = `/${collectionName}/{${identityField}}`;\n\n return {\n [collectionPath]: {\n get: {\n operationId: `list${resourceName}s`,\n summary: `List ${resourceName} resources`,\n responses: {\n 200: {\n description: 'Successful response',\n content: {\n 'application/json': {\n schema: {\n type: 'array',\n items: { $ref: ref },\n },\n },\n },\n },\n },\n },\n \n post: {\n operationId: `create${resourceName}`,\n summary: `Create a ${resourceName}`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n responses: {\n 201: {\n description: 'Created',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n },\n },\n \n },\n [itemPath]: {\n parameters: [\n {\n name: identityField,\n in: 'path',\n required: true,\n schema: identitySchema,\n },\n ],\n get: {\n operationId: `get${resourceName}`,\n summary: `Get a single ${resourceName}`,\n responses: {\n 200: {\n description: 'Successful response',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n 404: { description: `${resourceName} not found` },\n },\n },\n \n put: {\n operationId: `update${resourceName}`,\n summary: `Update a ${resourceName}`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n responses: {\n 200: {\n description: 'Successful update',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n },\n },\n delete: {\n operationId: `delete${resourceName}`,\n summary: `Delete a ${resourceName}`,\n responses: {\n 204: { description: 'Deleted' },\n },\n },\n \n },\n };\n}\n\nfunction pascalCase(input) {\n return input\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('') || 'Resource';\n}\n\nfunction kebabCase(input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase() || 'resource';\n}\n\nfunction pluralize(word) {\n if (word.endsWith('s')) {\n return word;\n }\n if (word.endsWith('y')) {\n return word.slice(0, -1) + 'ies';\n }\n return `${word}s`;\n}\n\nfunction clone(value) {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction looksLikeDmsSchema(candidate) {\n if (!candidate || typeof candidate !== 'object') {\n return false;\n }\n if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {\n return true;\n }\n if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {\n return true;\n }\n return false;\n}\n", + "func": "'use strict';\n\nconst DEFAULT_OPENAPI_VERSION = '3.0.1';\nconst DEFAULT_API_VERSION = '1.0.0';\nconst BASE_PREFIX = 'https://www.dev.ideas.cnpc/api/dms';\nconst DEFAULT_SERVICE_ID = 'well_kd_wellbore_ideas01';\nconst DEFAULT_API_SEGMENT = 'v1';\nconst FALLBACK_BASE_URL = `${BASE_PREFIX}/${DEFAULT_SERVICE_ID}/${DEFAULT_API_SEGMENT}`;\nconst FALLBACK_HEADERS = {\n 'Content-Type': 'application/json',\n 'Authorization': '1',\n 'Dataregion': 'ZZLH',\n};\nconst SERVICE_DOMAIN_CATALOG = {\n 'well_kd_wellbore_ideas01': {\n name: '井筒',\n id: 'well_kd_wellbore_ideas01',\n keywords: ['wb', 'wb_dr', 'wb_ml', 'wb_wl', 'wb_tp', 'wb_dh', 'wb_fr'],\n },\n 'geo_kd_res_ideas01': {\n name: '采油气',\n id: 'geo_kd_res_ideas01',\n keywords: ['pc', 'pc_op', 'pc_oe', 'pc_ge'],\n },\n 'kd_cr_ideas01': {\n name: '分析化验',\n id: 'kd_cr_ideas01',\n keywords: ['cr', 'cr_se'],\n },\n 'kd_rs_ideas01': {\n name: '油气藏',\n id: 'kd_rs_ideas01',\n keywords: ['rs', 'rs_rd', 'rs_rm', 'rs_gs', 'rs_in'],\n },\n};\n\nlet schema;\ntry {\n // 优先使用 HTTP In 提供的 req.body.schema,缺省时回退到 msg.payload。\n const schemaInput = extractSchemaInput(msg);\n schema = parseSchema(schemaInput);\n} catch (error) {\n node.error(`DMS -> Swagger 解析失败:${error.message}`, msg);\n msg.error = error.message;\n return msg;\n}\n\nconst resourceTitle = typeof schema.title === 'string' && schema.title.trim()\n ? schema.title.trim()\n : 'DMS Resource';\n\nconst resourceName = pascalCase(resourceTitle);\nconst collectionName = pluralize(kebabCase(resourceTitle));\nconst identityField = Array.isArray(schema.identityId) && schema.identityId.length > 0\n ? String(schema.identityId[0])\n : 'id';\n\nconst schemaComponent = buildComponentSchema(resourceName, schema);\nconst identitySchema = schemaComponent.properties && schemaComponent.properties[identityField]\n ? clone(schemaComponent.properties[identityField])\n : { type: 'string' };\n\nconst openApiDocument = {\n openapi: DEFAULT_OPENAPI_VERSION,\n info: {\n title: `${resourceTitle} API`,\n version: DEFAULT_API_VERSION,\n description: schema.description || `${schema.$id || ''}`.trim(),\n 'x-dms-sourceId': schema.$id || undefined,\n },\n paths: buildCrudPaths({\n collectionName,\n resourceName,\n identityField,\n identitySchema,\n }),\n components: {\n schemas: {\n [resourceName]: schemaComponent,\n },\n },\n};\n\nconst crudConfig = Object.assign({}, msg.crudConfig || {});\n\nconst dmsMeta = extractDmsMeta(msg);\nmsg.dms_meta = dmsMeta;\n\nconst serviceInfo = resolveServiceInfo(dmsMeta && dmsMeta.domain);\nconst apiVersionSegment = DEFAULT_API_SEGMENT;\nconst serviceId = serviceInfo.id;\n\nconst { resourceSegment, versionSegment } = deriveResourceSegments(dmsMeta, schema, collectionName);\nconst listPath = buildListPath(resourceSegment, versionSegment);\nconst singlePath = buildSinglePath(resourceSegment);\nconst detailPath = buildDetailPath(resourceSegment, versionSegment, identityField);\n\nif (!crudConfig.baseUrl) {\n crudConfig.baseUrl = `${BASE_PREFIX}/${serviceId}/${apiVersionSegment}`;\n}\n\nconst headers = Object.assign({}, FALLBACK_HEADERS, crudConfig.headers || {});\nconst dataRegionValue = extractDataRegion(schema, headers.Dataregion);\n\nif (dataRegionValue) {\n headers.Dataregion = dataRegionValue;\n crudConfig.dataRegion = dataRegionValue;\n}\n\ncrudConfig.headers = headers;\ncrudConfig.identityField = crudConfig.identityField || identityField;\n\ncrudConfig.service = crudConfig.service || {\n id: serviceId,\n name: serviceInfo.name,\n};\n\ncrudConfig.list = crudConfig.list || {};\nif (!crudConfig.list.path) {\n crudConfig.list.path = listPath;\n}\ncrudConfig.list.method = crudConfig.list.method || 'GET';\n\ncrudConfig.create = crudConfig.create || {};\nif (!crudConfig.create.path) {\n crudConfig.create.path = singlePath;\n}\ncrudConfig.create.method = crudConfig.create.method || 'POST';\n\ncrudConfig.delete = crudConfig.delete || {};\nif (!crudConfig.delete.path) {\n crudConfig.delete.path = singlePath;\n}\ncrudConfig.delete.method = crudConfig.delete.method || 'POST';\n\ncrudConfig.detailPath = crudConfig.detailPath || detailPath;\ncrudConfig.version = crudConfig.version || versionSegment;\n\nmsg.crudConfig = crudConfig;\nmsg.headers = Object.assign({}, headers);\n\nmsg.oas_def = openApiDocument;\nmsg.payload = openApiDocument;\nreturn msg;\n\nfunction extractDataRegion(dmsSchema, fallback) {\n if (!dmsSchema || typeof dmsSchema !== 'object') {\n return fallback;\n }\n\n if (typeof dmsSchema.dataRegion === 'string' && dmsSchema.dataRegion.trim()) {\n return dmsSchema.dataRegion.trim();\n }\n\n if (dmsSchema.defaultShow && Array.isArray(dmsSchema.defaultShow)) {\n const candidate = dmsSchema.defaultShow.find(item => item && typeof item === 'string' && item.toLowerCase().includes('dataregion'));\n if (candidate) {\n return fallback;\n }\n }\n\n const props = dmsSchema.properties;\n if (props && typeof props === 'object' && props.dataRegion) {\n if (typeof props.dataRegion.default === 'string' && props.dataRegion.default.trim()) {\n return props.dataRegion.default.trim();\n }\n }\n\n return fallback;\n}\n\nfunction extractSchemaInput(message) {\n if (message && message.req && message.req.body && typeof message.req.body === 'object') {\n if (message.req.body.schema !== undefined) {\n return message.req.body.schema;\n }\n if (looksLikeDmsSchema(message.req.body)) {\n return message.req.body;\n }\n }\n\n if (message && message.payload !== undefined) {\n if (message.payload && typeof message.payload === 'object') {\n if (message.payload.schema !== undefined) {\n return message.payload.schema;\n }\n if (looksLikeDmsSchema(message.payload)) {\n return message.payload;\n }\n }\n return message.payload;\n }\n\n throw new Error('未找到schema,请在请求体的schema字段或msg.payload中提供');\n}\n\nfunction parseSchema(source) {\n if (typeof source === 'string') {\n try {\n return JSON.parse(source);\n } catch (error) {\n throw new Error(`JSON 解析失败:${error.message}`);\n }\n }\n\n if (!source || typeof source !== 'object') {\n throw new Error('schema 必须是 DMS 定义对象或 JSON 字符串');\n }\n\n return source;\n}\n\nfunction buildComponentSchema(resourceName, dmsSchema) {\n const { properties = {}, required = [], groupView, identityId, naturalKey, defaultShow } = dmsSchema;\n const openApiProps = {};\n\n for (const [propName, propSchema] of Object.entries(properties)) {\n openApiProps[propName] = mapProperty(propSchema);\n }\n\n return {\n type: 'object',\n required: Array.isArray(required) ? required.slice() : [],\n properties: openApiProps,\n description: dmsSchema.description || dmsSchema.title || resourceName,\n 'x-dms-groupView': groupView || undefined,\n 'x-dms-identityId': identityId || undefined,\n 'x-dms-naturalKey': naturalKey || undefined,\n 'x-dms-defaultShow': defaultShow || undefined,\n };\n}\n\nfunction mapProperty(propSchema) {\n if (!propSchema || typeof propSchema !== 'object') {\n return { type: 'string' };\n }\n\n const result = {};\n\n const type = normalizeType(propSchema.type);\n result.type = type.type;\n if (type.format) {\n result.format = type.format;\n }\n if (type.items) {\n result.items = type.items;\n }\n\n if (propSchema.description) {\n result.description = propSchema.description;\n } else if (propSchema.title) {\n result.description = propSchema.title;\n }\n\n if (propSchema.enum) {\n result.enum = propSchema.enum.slice();\n }\n\n if (propSchema.default !== undefined) {\n result.default = propSchema.default;\n }\n\n if (propSchema.mask) {\n result['x-dms-mask'] = propSchema.mask;\n }\n if (propSchema.geom) {\n result['x-dms-geom'] = propSchema.geom;\n }\n if (propSchema.title) {\n result['x-dms-title'] = propSchema.title;\n }\n if (propSchema.type) {\n result['x-dms-originalType'] = propSchema.type;\n }\n\n return result;\n}\n\nfunction normalizeType(typeValue) {\n const normalized = typeof typeValue === 'string' ? typeValue.toLowerCase() : undefined;\n\n switch (normalized) {\n case 'number':\n case 'integer':\n case 'long':\n case 'float':\n case 'double':\n return { type: 'number' };\n case 'boolean':\n return { type: 'boolean' };\n case 'array':\n return { type: 'array', items: { type: 'string' } };\n case 'date':\n return { type: 'string', format: 'date' };\n case 'date-time':\n return { type: 'string', format: 'date-time' };\n case 'object':\n return { type: 'object' };\n case 'string':\n default:\n return { type: 'string' };\n }\n}\n\nfunction buildCrudPaths({ collectionName, resourceName, identityField, identitySchema }) {\n const ref = `#/components/schemas/${resourceName}`;\n const collectionPath = `/${collectionName}`;\n const itemPath = `/${collectionName}/{${identityField}}`;\n\n return {\n [collectionPath]: {\n get: {\n operationId: `list${resourceName}s`,\n summary: `List ${resourceName} resources`,\n responses: {\n 200: {\n description: 'Successful response',\n content: {\n 'application/json': {\n schema: {\n type: 'array',\n items: { $ref: ref },\n },\n },\n },\n },\n },\n },\n\n post: {\n operationId: `create${resourceName}`,\n summary: `Create a ${resourceName}`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n responses: {\n 201: {\n description: 'Created',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n },\n },\n\n },\n [itemPath]: {\n parameters: [\n {\n name: identityField,\n in: 'path',\n required: true,\n schema: identitySchema,\n },\n ],\n get: {\n operationId: `get${resourceName}`,\n summary: `Get a single ${resourceName}`,\n responses: {\n 200: {\n description: 'Successful response',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n 404: { description: `${resourceName} not found` },\n },\n },\n\n put: {\n operationId: `update${resourceName}`,\n summary: `Update a ${resourceName}`,\n requestBody: {\n required: true,\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n responses: {\n 200: {\n description: 'Successful update',\n content: {\n 'application/json': {\n schema: { $ref: ref },\n },\n },\n },\n },\n },\n delete: {\n operationId: `delete${resourceName}`,\n summary: `Delete a ${resourceName}`,\n responses: {\n 204: { description: 'Deleted' },\n },\n },\n\n },\n };\n}\n\nfunction pascalCase(input) {\n return input\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .split(' ')\n .filter(Boolean)\n .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('') || 'Resource';\n}\n\nfunction kebabCase(input) {\n return input\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .toLowerCase() || 'resource';\n}\n\nfunction pluralize(word) {\n if (word.endsWith('s')) {\n return word;\n }\n if (word.endsWith('y')) {\n return word.slice(0, -1) + 'ies';\n }\n return `${word}s`;\n}\n\nfunction clone(value) {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction looksLikeDmsSchema(candidate) {\n if (!candidate || typeof candidate !== 'object') {\n return false;\n }\n if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {\n return true;\n }\n if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {\n return true;\n }\n return false;\n}\n\nfunction extractDmsMeta(message) {\n const fromReq = message && message.req && message.req.body && message.req.body.dms_meta;\n if (fromReq && typeof fromReq === 'object') {\n return clone(fromReq);\n }\n const fromMsg = message && message.dms_meta;\n if (fromMsg && typeof fromMsg === 'object') {\n return clone(fromMsg);\n }\n return null;\n}\n\nfunction resolveServiceInfo(domain) {\n if (!domain || typeof domain !== 'string') {\n return SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];\n }\n const normalized = domain.toLowerCase();\n let best = null;\n for (const entry of Object.values(SERVICE_DOMAIN_CATALOG)) {\n const matched = entry.keywords.some(keyword => {\n if (typeof keyword !== 'string') return false;\n const normKeyword = keyword.toLowerCase();\n return normalized.includes(normKeyword) || normKeyword.includes(normalized);\n });\n if (matched) {\n best = entry;\n break;\n }\n }\n return best || SERVICE_DOMAIN_CATALOG[DEFAULT_SERVICE_ID];\n}\n\nfunction deriveResourceSegments(meta, schema, collectionName) {\n const defaultResource = (collectionName || '').replace(/^\\//, '');\n let resourceSegment = defaultResource;\n let versionSegment = null;\n\n if (meta && typeof meta === 'object') {\n if (typeof meta.id === 'string' && meta.id.trim()) {\n const parts = meta.id.trim().split('.');\n if (parts.length > 0 && parts[0]) {\n resourceSegment = parts[0];\n }\n if (parts.length > 1) {\n versionSegment = parts.slice(1).join('.');\n }\n } else if (typeof meta.name === 'string' && meta.name.trim()) {\n resourceSegment = meta.name.trim();\n }\n }\n\n if (!resourceSegment && schema && schema.title) {\n resourceSegment = kebabCase(schema.title);\n }\n\n return {\n resourceSegment: resourceSegment || defaultResource || 'resource',\n versionSegment: versionSegment,\n };\n}\n\nfunction buildListPath(resourceSegment, versionSegment) {\n if (versionSegment) {\n return `/${resourceSegment}/${versionSegment}`;\n }\n return `/${resourceSegment}`;\n}\n\nfunction buildSinglePath(resourceSegment) {\n return `/${resourceSegment}`;\n}\n\nfunction buildDetailPath(resourceSegment, versionSegment, identityField) {\n if (versionSegment) {\n return `/${resourceSegment}/${versionSegment}/{${identityField}}`;\n }\n return `/${resourceSegment}/{${identityField}}`;\n}\n\nfunction looksLikeDmsSchema(candidate) {\n if (!candidate || typeof candidate !== 'object') {\n return false;\n }\n if (candidate.properties && typeof candidate.properties === 'object' && candidate.title) {\n return true;\n }\n if (candidate.$id && candidate.type && candidate.type.toLowerCase && candidate.type.toLowerCase() === 'object') {\n return true;\n }\n return false;\n}\n\n", "outputs": 1, "timeout": 0, "noerr": 0,