@redocly/realm 0.136.0-next.7 → 0.136.0-next.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/server/api-routes/helpers/enhance-context.js +1 -1
  3. package/dist/server/api-routes/run-api-routes-worker.js +1 -1
  4. package/dist/server/config/env-config.d.ts +0 -2
  5. package/dist/server/config/env-schema.d.ts +0 -5
  6. package/dist/server/config/env-schemas/feature-flags.d.ts +0 -4
  7. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  8. package/dist/server/plugins/catalog-entities/database/repositories/catalog-entities-repository.js +1 -1
  9. package/dist/server/plugins/mcp/codemode/capabilities/types.d.ts +1 -1
  10. package/dist/server/plugins/mcp/codemode/capabilities/utils.d.ts +1 -1
  11. package/dist/server/plugins/mcp/codemode/code-audit.d.ts +12 -0
  12. package/dist/server/plugins/mcp/codemode/code-audit.js +1 -0
  13. package/dist/server/plugins/mcp/codemode/code-execution-telemetry.d.ts +11 -0
  14. package/dist/server/plugins/mcp/codemode/code-execution-telemetry.js +1 -0
  15. package/dist/server/plugins/mcp/codemode/tools/describe-tools.d.ts +2 -3
  16. package/dist/server/plugins/mcp/codemode/tools/execute-schema.d.ts +1 -1
  17. package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
  18. package/dist/server/plugins/mcp/constants.d.ts +1 -1
  19. package/dist/server/plugins/mcp/constants.js +1 -1
  20. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +19 -18
  21. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +2 -2
  22. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  23. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.d.ts +2 -1
  24. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.js +1 -1
  25. package/dist/server/plugins/mcp/gateway-mcp/fetch/normalize-path.d.ts +6 -0
  26. package/dist/server/plugins/mcp/gateway-mcp/fetch/normalize-path.js +1 -0
  27. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -1
  28. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.d.ts +8 -0
  29. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/rate-limit-telemetry.js +1 -0
  30. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +1 -1
  31. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  32. package/dist/server/plugins/mcp/handlers/errors.js +1 -1
  33. package/dist/server/plugins/mcp/servers/docs-server.d.ts +2 -3
  34. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  35. package/dist/server/plugins/mcp/servers/mcp-server.d.ts +10 -14
  36. package/dist/server/plugins/mcp/servers/mcp-server.js +1 -1
  37. package/dist/server/plugins/mcp/types.d.ts +6 -6
  38. package/dist/server/plugins/mcp/utils/json-schema-validator.d.ts +9 -0
  39. package/dist/server/plugins/mcp/utils/json-schema-validator.js +1 -0
  40. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  41. package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
  42. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.d.ts +9 -1
  43. package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
  44. package/dist/server/plugins/search/ai-indexer/prepare-semantic-documents.js +2 -2
  45. package/dist/server/plugins/search/engines/flexsearch/index.js +1 -1
  46. package/dist/server/plugins/search/engines/flexsearch/search-index.js +1 -1
  47. package/dist/server/plugins/search/engines/typesense/index.js +1 -1
  48. package/dist/server/tools/notifiers/logger.js +1 -1
  49. package/dist/server/tools/notifiers/reporter.js +6 -6
  50. package/dist/server/types/plugins/common.d.ts +1 -1
  51. package/dist/server/web-server/auth.js +3 -3
  52. package/dist/server/web-server/middleware/telemetry-middleware.js +1 -1
  53. package/dist/server/web-server/routes/catalog/bff-catalog-related-entities.js +1 -1
  54. package/dist/server/web-server/routes/catalog/bff-catalog-revisions.js +1 -1
  55. package/dist/server/web-server/routes/catalog/bff-catalog.js +1 -1
  56. package/dist/server/web-server/routes/catalog/catalog-relations.js +1 -1
  57. package/dist/server/web-server/routes/catalog/catalog.js +1 -1
  58. package/dist/server/web-server/routes/dynamic-route.js +1 -1
  59. package/dist/server/web-server/routes/mcp-routes/a2a.js +1 -1
  60. package/dist/server/web-server/routes/mcp-routes/agent-card.js +1 -1
  61. package/dist/server/web-server/routes/mcp-routes/mcp-server-card.js +1 -1
  62. package/dist/server/web-server/routes/page-data.js +1 -1
  63. package/dist/server/web-server/routes/search.js +1 -1
  64. package/dist/server/web-server/routes/skills/skill-markdown.js +1 -1
  65. package/dist/server/web-server/routes/skills/skills-index.js +1 -1
  66. package/dist/utils/url/parse-preview-url.d.ts +22 -0
  67. package/dist/utils/url/parse-preview-url.js +1 -0
  68. package/package.json +9 -10
  69. package/dist/server/plugins/mcp/handlers/sse-keepalive.d.ts +0 -15
  70. package/dist/server/plugins/mcp/handlers/sse-keepalive.js +0 -3
  71. package/dist/server/plugins/mcp/servers/session-manager.d.ts +0 -15
  72. package/dist/server/plugins/mcp/servers/session-manager.js +0 -1
@@ -1,6 +1,5 @@
1
1
  import type { ApiFunctionsContext, ApiFunctionsContextMethods, PageStaticData } from '@redocly/config';
2
- import type { CallToolResult, RequestId, RequestInfo, RequestMeta } from '@redocly/mcp-typescript-sdk/types.js';
3
- import type { AuthInfo } from '@redocly/mcp-typescript-sdk/server/types.js';
2
+ import type { AuthInfo, CallToolResult, RequestId, RequestMeta, StandardSchemaWithJSON, ToolCallback } from '@modelcontextprotocol/server';
4
3
  import type { OpenAPIPath, OpenAPIServer, OpenAPIOperation } from '@redocly/api-docs';
5
4
  import type { McpErrorCodes, McpServerType } from './constants';
6
5
  import type { McpToolSchema } from '../../types';
@@ -79,11 +78,12 @@ export type SemanticSearchResult = {
79
78
  content: string;
80
79
  };
81
80
  export type McpToolWorkerResponse = CallToolResult;
81
+ export type ToolArgs = Record<string, unknown>;
82
+ export type McpToolCallback = ToolCallback<StandardSchemaWithJSON<ToolArgs, ToolArgs>>;
82
83
  export type McpToolExtra = {
83
84
  sessionId?: string;
84
85
  authInfo?: AuthInfo;
85
86
  requestId: RequestId;
86
- requestInfo?: RequestInfo;
87
87
  _meta?: RequestMeta;
88
88
  };
89
89
  export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | keyof ApiFunctionsContextMethods> & {
@@ -99,15 +99,15 @@ export type McpToolContext = Omit<ApiFunctionsContext, 'telemetry' | 'getKv' | k
99
99
  clientIp?: string;
100
100
  upstreamSecrets?: Record<string, StoredSecret>;
101
101
  };
102
- export type McpToolHandler<TArgs extends Record<string, unknown> = Record<string, unknown>> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse>;
102
+ export type McpToolHandler<TArgs extends ToolArgs = ToolArgs> = (args: TArgs, context: McpToolContext, extra: McpToolExtra) => Promise<McpToolWorkerResponse>;
103
103
  export type McpToolIsAvailable = (context: McpToolContext) => boolean | Promise<boolean>;
104
- export type McpToolHandlerExtended<TArgs extends Record<string, unknown> = Record<string, unknown>> = {
104
+ export type McpToolHandlerExtended<TArgs extends ToolArgs = ToolArgs> = {
105
105
  execute: McpToolHandler<TArgs>;
106
106
  isAvailable?: McpToolIsAvailable;
107
107
  };
108
108
  export type McpToolExecutionParams = {
109
109
  toolName: string;
110
- args: Record<string, unknown>;
110
+ args: ToolArgs;
111
111
  context: McpToolContext;
112
112
  extra: McpToolExtra;
113
113
  };
@@ -0,0 +1,9 @@
1
+ import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv';
2
+ /**
3
+ * Validates tool arguments with the same engine and options the forked SDK
4
+ * (@redocly/mcp-typescript-sdk) used internally, so migrating to the upstream
5
+ * v2 SDK does not change which arguments are accepted. The SDK default would
6
+ * be a lenient draft-2020-12 engine instead.
7
+ */
8
+ export declare const mcpJsonSchemaValidator: AjvJsonSchemaValidator;
9
+ //# sourceMappingURL=json-schema-validator.d.ts.map
@@ -0,0 +1 @@
1
+ import{AjvJsonSchemaValidator as o}from"@modelcontextprotocol/server/validators/ajv";import r from"@redocly/ajv";const t=new o(new r({allErrors:!0}));export{t as mcpJsonSchemaValidator};
@@ -1 +1 @@
1
- import{EntitlementsProvider as e}from"../../../entitlements/entitlements-provider.js";import{envConfig as n}from"../../../config/env-config.js";function t(){return e.instance().entitlements?.mcpMode==="code"}function o(){return!!e.instance().canAccessFeature("mcpGateway")&&t()}function i(){return(n.REDOCLY_EXP_MCP_ELICITATION_ENABLED??!0)&&o()}function a(){return t()?"code":"tools"}export{a as getMcpMode,t as isMcpCodeModeEnabled,i as isMcpElicitationEnabled,o as isMcpGatewayEnabled};
1
+ import{EntitlementsProvider as e}from"../../../entitlements/entitlements-provider.js";function t(){return e.instance().entitlements?.mcpMode==="code"}function n(){return!!e.instance().canAccessFeature("mcpGateway")&&t()}function c(){return n()}function r(){return t()?"code":"tools"}export{r as getMcpMode,t as isMcpCodeModeEnabled,c as isMcpElicitationEnabled,n as isMcpGatewayEnabled};
@@ -1 +1 @@
1
- import{telemetry as d}from"../../../telemetry/index.js";import{mcpToolHandlers as w}from"../../../../client/mcp-tool-handlers-entry.js";import{McpServerType as f}from"../constants.js";import{reportMcpToolCalled as y,reportMcpToolError as u}from"./mcp-tool-telemetry.js";async function b(r){d.initialize();const{toolName:o,args:c,context:i,extra:t}=r,a=i?.mcpMode??"tools",n=performance.now();try{const e=w[o];if(!e)throw new Error(`Unknown MCP tool: ${o}`);const{default:h}=await e(),l=h[o];if(!l)throw new Error(`MCP tool module does not export a handler for "${o}". Expected \`export default { '${o}': handler }\`.`);const s=await(typeof l=="function"?l:l.execute)(c,i,t);if(!x(s))throw new Error(`MCP tool "${o}" returned an invalid result.`);const m=Math.round(performance.now()-n);if(s.isError)u({tool:o,message:s.content.map(({text:p})=>p).join(" "),mode:a,serverType:f.Docs,durationMs:m});else{const p=s.content.map(({text:M})=>M).join("").length;y({tool:o,mode:a,serverType:f.Docs,durationMs:m,outputLength:p})}return s}catch(e){throw u({tool:o,message:e instanceof Error?e.message:String(e),mode:a,serverType:f.Docs,durationMs:Math.round(performance.now()-n),stack:e instanceof Error&&e.stack||""}),e}}function x(r){return!r||typeof r!="object"?!1:Array.isArray(r.content)}async function k(r){d.initialize();const{toolName:o,context:c}=r,i=performance.now();try{const t=w[o];if(!t)return!1;const{default:a}=await t(),n=a[o];return n?typeof n=="object"&&typeof n.isAvailable=="function"?await n.isAvailable(c):!0:!1}catch(t){throw u({tool:o,message:"Failed to check if MCP tool is available: "+(t instanceof Error?t.message:String(t)),mode:c?.mcpMode??"tools",serverType:f.Docs,durationMs:Math.round(performance.now()-i),stack:t instanceof Error&&t.stack||""}),t}}export{b as executeMcpTool,k as isMcpToolAvailable};
1
+ import{telemetry as m}from"../../../telemetry/index.js";import{mcpToolHandlers as d}from"../../../../client/mcp-tool-handlers-entry.js";import{McpServerType as f}from"../constants.js";import{EXECUTE_TOOL_NAME as y}from"../codemode/constants.js";import{reportMcpToolCalled as x,reportMcpToolError as p}from"./mcp-tool-telemetry.js";function w(e){return e.content.map(o=>o.type==="text"?o.text:"")}async function _(e){m.initialize();const{toolName:o,args:c,context:i,extra:r}=e,s=i?.mcpMode??"tools",n=performance.now();try{const t=d[o];if(!t)throw new Error(`Unknown MCP tool: ${o}`);const{default:h}=await t(),l=h[o];if(!l)throw new Error(`MCP tool module does not export a handler for "${o}". Expected \`export default { '${o}': handler }\`.`);const a=await(typeof l=="function"?l:l.execute)(c,i,r);if(!E(a))throw new Error(`MCP tool "${o}" returned an invalid result.`);const u=Math.round(performance.now()-n);if(a.isError)p({tool:o,message:w(a).join(" "),mode:s,serverType:f.Docs,durationMs:u,errorClass:o===y?"sandbox_error":"tool_error"});else{const M=w(a).join("").length;x({tool:o,mode:s,serverType:f.Docs,durationMs:u,outputLength:M})}return a}catch(t){throw p({tool:o,message:t instanceof Error?t.message:String(t),mode:s,serverType:f.Docs,durationMs:Math.round(performance.now()-n),stack:t instanceof Error&&t.stack||"",errorClass:"server_error"}),t}}function E(e){return!e||typeof e!="object"?!1:Array.isArray(e.content)}async function k(e){m.initialize();const{toolName:o,context:c}=e,i=performance.now();try{const r=d[o];if(!r)return!1;const{default:s}=await r(),n=s[o];return n?typeof n=="object"&&typeof n.isAvailable=="function"?await n.isAvailable(c):!0:!1}catch(r){throw p({tool:o,message:"Failed to check if MCP tool is available: "+(r instanceof Error?r.message:String(r)),mode:c?.mcpMode??"tools",serverType:f.Docs,durationMs:Math.round(performance.now()-i),stack:r instanceof Error&&r.stack||"",errorClass:"server_error"}),r}}export{_ as executeMcpTool,k as isMcpToolAvailable};
@@ -5,12 +5,20 @@ type McpToolEvent = {
5
5
  serverType: McpServerType;
6
6
  durationMs: number;
7
7
  };
8
+ /**
9
+ * Who an error belongs to. `tool_error` — the caller asked for something that does not exist or
10
+ * does not validate. `sandbox_error` — the model's own generated code threw in code mode.
11
+ * `server_error` — Redocly code failed. Reported explicitly so consumers never have to infer it
12
+ * from whether a stack trace happens to be attached.
13
+ */
14
+ export type McpErrorClass = 'tool_error' | 'sandbox_error' | 'server_error';
8
15
  export declare function reportMcpToolCalled({ tool, mode, serverType, durationMs, outputLength, }: McpToolEvent & {
9
16
  outputLength: number;
10
17
  }): void;
11
- export declare function reportMcpToolError({ tool, mode, serverType, durationMs, message, stack, }: McpToolEvent & {
18
+ export declare function reportMcpToolError({ tool, mode, serverType, durationMs, message, stack, errorClass, }: McpToolEvent & {
12
19
  message: string;
13
20
  stack?: string;
21
+ errorClass: McpErrorClass;
14
22
  }): void;
15
23
  export {};
16
24
  //# sourceMappingURL=mcp-tool-telemetry.d.ts.map
@@ -1 +1 @@
1
- import{telemetry as s}from"../../../telemetry/index.js";import{serverAttributes as l}from"../utils/telemetry-attributes.js";function u({tool:r,mode:o,serverType:e,durationMs:t,outputLength:p}){s.sendMcpToolCalledMessage([{...l(e),tool:r,mode:o,output_length:p,duration_ms:t}])}function a({tool:r,mode:o,serverType:e,durationMs:t,message:p,stack:n=""}){s.sendMcpErrorMessage([{...l(e),tool:r,message:p,stack:n,mode:o,duration_ms:t}])}export{u as reportMcpToolCalled,a as reportMcpToolError};
1
+ import{telemetry as p}from"../../../telemetry/index.js";import{serverAttributes as l}from"../utils/telemetry-attributes.js";function m({tool:r,mode:o,serverType:e,durationMs:t,outputLength:s}){p.sendMcpToolCalledMessage([{...l(e),tool:r,mode:o,output_length:s,duration_ms:t}])}function u({tool:r,mode:o,serverType:e,durationMs:t,message:s,stack:n="",errorClass:a}){p.sendMcpErrorMessage([{...l(e),tool:r,message:s,stack:n,error_class:a,mode:o,duration_ms:t}])}export{m as reportMcpToolCalled,u as reportMcpToolError};
@@ -1,3 +1,3 @@
1
- import p from"node:path";import{existsSync as X}from"node:fs";import{writeFile as D,access as N,rm as k,constants as B}from"fs/promises";import{REDOCLY_ROUTE_RBAC as _,REDOCLY_TEAMS_RBAC as h}from"@redocly/config";import U from"picomatch";import{combineUrls as j}from"@redocly/theme/core/utils";import{DEFAULT_LOCALE_PLACEHOLDER as E}from"../../../../constants/common.js";import{AI_INDEX_EXPORT_FOLDER as G}from"../../../constants/plugins/search.js";import{LLMS_TXT_FILE_NAME as H}from"../../../constants/common.js";import{envConfig as C}from"../../../config/env-config.js";import{logger as c}from"../../../tools/notifiers/logger.js";import{shaHexShort as Y}from"../../../utils/crypto/sha-hex-short.js";import{promiseMapLimit as P}from"../../../utils/async/promise-map-limit.js";import{getLlmsTxtMdPathBySlug as K}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{buildAgentFeedbackLlmsFooter as J}from"../../../utils/llmstxt/agent-feedback-llms-footer.js";import{validateLLMsTxtConfig as V,generateLLMsTxt as W}from"../llmstxt/index.js";import{ensureDir as F}from"../../../utils/fs.js";import{isResourcePubliclyAccessible as q,extractTeamsFromScopeItems as z,getRbacTeamsListForResource as Q}from"../../../utils/rbac.js";const w=20,S="llms.txt:";async function Dt(t,i,{embeddingsEnabled:o,llmstxtEnabled:n}){const l=p.join(t.outdir,H);if(!n){try{await N(l,B.W_OK),await k(l)}catch{}if(!o)return}o&&c.info("Preparing semantic documents..."),n&&c.info(`${S} Generating llms.txt files...`);const T=c.startTiming(),a=c.startTiming(),m=t.getGlobalConfig("seo"),f=m?.llmstxt,d=C.REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED??!1,M=p.resolve(t.outdir,G),u=t.getConfig(),b=u.access?.rbac??{},O=t.getAllRoutes(),y=u?.l10n?.defaultLocale||u?.i18n?.defaultLocale||E,I=(f?.excludeFiles||[]).map(e=>U(e)),R=[];if(await P(O,w,async e=>{if(e.excludeFromSearch)return;const v=await Z(e,t),x=await tt(e,v,i,t);if(x){if(n)for(const s of await x.getLLMsTxts()){if(I.some($=>$(s.fsPath)))continue;const g=K(s.slug),r=p.join(t.outdir,g);F(r);const L=d?J(m,s.slug):void 0,A=L?`
1
+ import L from"node:path";import{existsSync as N}from"node:fs";import{writeFile as R,access as X,rm as B,constants as k}from"fs/promises";import{REDOCLY_ROUTE_RBAC as _,REDOCLY_TEAMS_RBAC as h}from"@redocly/config";import j from"picomatch";import{DEFAULT_LOCALE_PLACEHOLDER as E}from"../../../../constants/common.js";import{AI_INDEX_EXPORT_FOLDER as G}from"../../../constants/plugins/search.js";import{LLMS_TXT_FILE_NAME as Y}from"../../../constants/common.js";import{envConfig as C}from"../../../config/env-config.js";import{logger as c}from"../../../tools/notifiers/logger.js";import{shaHexShort as H}from"../../../utils/crypto/sha-hex-short.js";import{promiseMapLimit as P}from"../../../utils/async/promise-map-limit.js";import{getLlmsTxtMdPathBySlug as K}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{buildAgentFeedbackLlmsFooter as U}from"../../../utils/llmstxt/agent-feedback-llms-footer.js";import{validateLLMsTxtConfig as J,generateLLMsTxt as V}from"../llmstxt/index.js";import{ensureDir as F}from"../../../utils/fs.js";import{isResourcePubliclyAccessible as W,extractTeamsFromScopeItems as q,getRbacTeamsListForResource as z}from"../../../utils/rbac.js";const w=20,S="llms.txt:";async function At(t,i,{embeddingsEnabled:o,llmstxtEnabled:s}){const l=L.join(t.outdir,Y);if(!s){try{await X(l,k.W_OK),await B(l)}catch{}if(!o)return}o&&c.info("Preparing semantic documents..."),s&&c.info(`${S} Generating llms.txt files...`);const p=c.startTiming(),a=c.startTiming(),m=t.getGlobalConfig("seo"),f=m?.llmstxt,T=C.REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED??!1,M=L.resolve(t.outdir,G),u=t.getConfig(),O=u.access?.rbac??{},y=t.getAllRoutes(),b=u?.l10n?.defaultLocale||u?.i18n?.defaultLocale||E,I=(f?.excludeFiles||[]).map(e=>j(e)),d=[];if(await P(y,w,async e=>{if(e.excludeFromSearch)return;const $=await Q(e,t),x=await Z(e,$,i,t);if(x){if(s)for(const n of await x.getLLMsTxts()){if(I.some(v=>v(n.fsPath)))continue;const A=K(n.slug),r=L.join(t.outdir,A);F(r);const g=T?U(m,n.slug):void 0,D=g?`
2
2
 
3
- ${L}`:"";await D(r,`${s.content}${A}`),t.setResourceResponseHeaders(g,[{name:"X-Robots-Tag",value:"noindex, follow"},...u.seo?.siteUrl?[{name:"Link",value:`<${j(u.seo.siteUrl,s.slug)}>; rel="canonical"`}]:[]]),s.includeInLLMsTxt&&q(e,u)&&R.push(s)}if(o&&C.isBuildMode){const s=await x.getSearchDocuments();if(!s.length)return;const g=et(e,b),r=e.versions?.find(({active:A})=>A),L=r&&{folder:r.folderId,label:r.label,default:r.default};await ot(s,e.fsPath,g,M,y,L)}}}),o&&c.infoTime(a,"Semantic search documents prepared"),R.length)try{V(f);const e=await W(R,f,{title:m?.title,description:m?.description},i);F(l),await D(l,e),c.infoTime(T,`${S} files generated`)}catch(e){c.error(`${S} Failed to generate llms.txt file. ${e.message}`)}}async function Z(t,i){return t.getStaticData?t.getStaticData(t,i):{}}async function tt(t,i,o,n){if(t.getAiDocumentsStore)return t.getAiDocumentsStore(t,{...i,[h]:t[h],[_]:t[_]},o,n)}function et(t,i){const o=z(t?.[h]);return o?.length?o:Q(t,i)}async function ot(t,i,o,n,l,T){await P(t,w,async a=>{const m=`${Y(i+a.content)}.json`,f=F(p.join(n,m)),d=a.locale===E?l:a.locale;X(f)||await D(f,JSON.stringify({...a,rbacTeams:a.rbacTeams??o??[],version:T,locale:d}),"utf-8")})}export{Dt as prepareSemanticDocuments};
3
+ ${g}`:"";await R(r,`${n.content}${D}`),n.includeInLLMsTxt&&W(e,u)&&d.push(n)}if(o&&C.isBuildMode){const n=await x.getSearchDocuments();if(!n.length)return;const A=tt(e,O),r=e.versions?.find(({active:D})=>D),g=r&&{folder:r.folderId,label:r.label,default:r.default};await et(n,e.fsPath,A,M,b,g)}}}),o&&c.infoTime(a,"Semantic search documents prepared"),d.length)try{J(f);const e=await V(d,f,{title:m?.title,description:m?.description},i);F(l),await R(l,e),c.infoTime(p,`${S} files generated`)}catch(e){c.error(`${S} Failed to generate llms.txt file. ${e.message}`)}}async function Q(t,i){return t.getStaticData?t.getStaticData(t,i):{}}async function Z(t,i,o,s){if(t.getAiDocumentsStore)return t.getAiDocumentsStore(t,{...i,[h]:t[h],[_]:t[_]},o,s)}function tt(t,i){const o=q(t?.[h]);return o?.length?o:z(t,i)}async function et(t,i,o,s,l,p){await P(t,w,async a=>{const m=`${H(i+a.content)}.json`,f=F(L.join(s,m)),T=a.locale===E?l:a.locale;N(f)||await R(f,JSON.stringify({...a,rbacTeams:a.rbacTeams??o??[],version:p,locale:T}),"utf-8")})}export{At as prepareSemanticDocuments};
@@ -1 +1 @@
1
- import{readFile as w,readdir as y,appendFile as E}from"node:fs/promises";import m from"node:path";import{existsSync as F}from"node:fs";import{SEARCH_GROUP_FACET_FIELD as I,SEARCH_PRODUCT_FIELD as C,SEARCH_VERSION_FIELD as $}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as A,SEARCH_DATA_EXPORT_FOLDER as _,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as O}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as v}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as N}from"../../../../config/env-config.js";import{ensureDir as h}from"../../../../utils/index.js";import{FlexSearchIndex as j}from"./search-index.js";class J{#e=new Map;#s;#t=0;async initIndexSchema(e){this.#s={document:{id:"id",tag:"tags",index:A},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#n(e,s){const n=this.#e.get(e);if(n)return n.find(o=>o.id===s)}#o(e,s){let n=this.#n(e,s);return n||(n=new j(s,this.#s),this.#e.set(e,[...this.#e.get(e)??[],n])),n}async addDocuments(e,s){if(e.length){const{group:n,locale:o,outDir:a}=s,i=this.#o(o,n);for(const c of e)i.add(c),N.isBuildMode&&(this.#t++,this.#t>=O&&await this.exportDocuments(a))}}getIndexesSize(){return this.#e.size}async search(e,s,n){return await v("search",async o=>{const{query:a,locale:i,filter:c,loadMore:r}=e,l=this.#e.get(i)??[];let f=[],d="",x=["v:default"];if(o?.setAttribute("locale",i),c)for(const t of c)t.field===I?f=[...f,...t.values]:t.field===C?d=`p:${t.values[0]}`:t.field===$&&t.values.length&&t.values.length===2&&(x=[...x,`v:${t.values[0]}:${t.values[1]}`]);const g={auth:s,query:a,offset:0,SEARCH_GROUP_FACET_FIELD:I,product:d,versions:x},p=[];let u={facets:{},documents:{}};if(r){const t=this.#n(i,r.groupKey);t&&p.push(t.search({...g,offset:r.offset}))}else for(const t of l)f&&f.length?f.includes(t.id)&&p.push(t.search(g)):p.push(t.search(g));const R=await Promise.all(p);for(const t of R){u.documents={...u.documents,...t.documents};for(const[D,S]of Object.entries(t.facets))u.facets[D]?u.facets[D]=[...u.facets[D],...S]:u.facets[D]=S}return u})}async exportDocuments(e){const s='{"documents":[',n=h(m.join(e,_));for(const[o,a]of this.#e){const i=h(m.join(n,o));for(const c of a){const r=Array.from(c.documents);if(r.length===0)continue;const l=h(m.join(i,`${c.id}.json`)),f=!F(l),d=JSON.stringify(r).substring(1).slice(0,-1),x=f?s+d:","+d;await E(l,x,{encoding:"utf8"}),c.clearDocuments()}}this.#t=0}async exportIndexes(e){const s=h(m.join(e,_));for(const[n,o]of this.#e){const a=h(m.join(s,n));for(const i of o){const c=h(m.join(a,`${i.id}.json`)),r={};await i.export((f,d)=>{r[f]=d});const l=`],"index":${JSON.stringify(r)}}`;await E(c,l,{encoding:"utf8"})}}this.#e.clear()}async import(e){const s=`${e}/${_}`;if(!F(s))return;const n=await y(s);for(const o of n){const a=await y(`${s}/${o}`);for(const i of a){const c=`${s}/${o}/${i}`,r=JSON.parse(await w(c,"utf-8")),l=m.parse(i).name;await this.#o(o,l).import(r)}}}countFacets(e,s,n){const o={};for(const[a,i]of n)o[a]=i.values.map(c=>({value:c,count:0,isCounterVisible:!1}));return o}cleanupFacetValues(e){}}export{J as FlexSearch};
1
+ import{readFile as R,readdir as E,appendFile as F}from"node:fs/promises";import m from"node:path";import{existsSync as I}from"node:fs";import{SEARCH_GROUP_FACET_FIELD as S,SEARCH_PRODUCT_FIELD as w,SEARCH_VERSION_FIELD as C}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as $,SEARCH_DATA_EXPORT_FOLDER as g,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as O}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as v}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as j}from"../../../../config/env-config.js";import{ensureDir as h}from"../../../../utils/index.js";import{FlexSearchIndex as N}from"./search-index.js";class B{#e=new Map;#s;#t=0;async initIndexSchema(e){this.#s={document:{id:"id",tag:"tags",index:$},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#o(e,o){const i=this.#e.get(e);if(i)return i.find(t=>t.id===o)}#i(e,o){let i=this.#o(e,o);return i||(i=new N(o,this.#s),this.#e.set(e,[...this.#e.get(e)??[],i])),i}async addDocuments(e,o){if(e.length){const{group:i,locale:t,outDir:c}=o,n=this.#i(t,i);for(const r of e)n.add(r),j.isBuildMode&&(this.#t++,this.#t>=O&&await this.exportDocuments(c))}}getIndexesSize(){return this.#e.size}async search(e,o,i){return await v("com.redocly.search.performed",async t=>{t?.setAttribute("id","searchQueryId"),t?.setAttribute("object","searchQuery"),t?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:c,locale:n,filter:r,loadMore:a}=e,l=this.#e.get(n)??[];let f=[],d="",x=["v:default"];if(t?.setAttribute("locale",n),r)for(const s of r)s.field===S?f=[...f,...s.values]:s.field===w?d=`p:${s.values[0]}`:s.field===C&&s.values.length&&s.values.length===2&&(x=[...x,`v:${s.values[0]}:${s.values[1]}`]);const D={auth:o,query:c,offset:0,SEARCH_GROUP_FACET_FIELD:S,product:d,versions:x},p=[];let u={facets:{},documents:{}};if(a){const s=this.#o(n,a.groupKey);s&&p.push(s.search({...D,offset:a.offset}))}else for(const s of l)f&&f.length?f.includes(s.id)&&p.push(s.search(D)):p.push(s.search(D));const A=await Promise.all(p);for(const s of A){u.documents={...u.documents,...s.documents};for(const[y,_]of Object.entries(s.facets))u.facets[y]?u.facets[y]=[...u.facets[y],..._]:u.facets[y]=_}return u})}async exportDocuments(e){const o='{"documents":[',i=h(m.join(e,g));for(const[t,c]of this.#e){const n=h(m.join(i,t));for(const r of c){const a=Array.from(r.documents);if(a.length===0)continue;const l=h(m.join(n,`${r.id}.json`)),f=!I(l),d=JSON.stringify(a).substring(1).slice(0,-1),x=f?o+d:","+d;await F(l,x,{encoding:"utf8"}),r.clearDocuments()}}this.#t=0}async exportIndexes(e){const o=h(m.join(e,g));for(const[i,t]of this.#e){const c=h(m.join(o,i));for(const n of t){const r=h(m.join(c,`${n.id}.json`)),a={};await n.export((f,d)=>{a[f]=d});const l=`],"index":${JSON.stringify(a)}}`;await F(r,l,{encoding:"utf8"})}}this.#e.clear()}async import(e){const o=`${e}/${g}`;if(!I(o))return;const i=await E(o);for(const t of i){const c=await E(`${o}/${t}`);for(const n of c){const r=`${o}/${t}/${n}`,a=JSON.parse(await R(r,"utf-8")),l=m.parse(n).name;await this.#i(t,l).import(a)}}}countFacets(e,o,i){const t={};for(const[c,n]of i)t[c]=n.values.map(r=>({value:r,count:0,isCounterVisible:!1}));return t}cleanupFacetValues(e){}}export{B as FlexSearch};
@@ -1 +1 @@
1
- import I from"flexsearch";import{REDOCLY_TEAMS_RBAC as S}from"@redocly/config";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as L,SEARCH_LIMIT as w}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as D}from"../../../../telemetry/helpers/trace-step.js";import{highlightTextForSearch as p}from"../../../../utils/search/highlight-text-for-search.js";class y{#e;#t=new Map;#s=1;id;constructor(e,t){this.id=e,this.#e=new I.Document(t)}get documents(){return this.#t.entries()}get documentsCount(){return this.#s}clearDocuments(){this.#t.clear()}async export(e){await this.#e.export(e)}async import(e){const{documents:t,index:s}=e;this.#t.clear();for(const[r,i]of t)this.#t.set(r,i);for(const[r,i]of Object.entries(s))await this.#e.import(r,i)}add(e){e.tags||(e.tags=[]),this.#t.set(this.#s,e[S]?e:{...e,[S]:void 0}),this.#e.add(this.#s,e),this.#s++}async search(e){return await D("search",async t=>{const{query:s,offset:r,auth:i,product:o,versions:a}=e,n=[],c=i.teams,h={limit:w,offset:r};let m=new Map,u=await this.#e.searchAsync(s,{...h,tag:c});if(o){const l=await this.#e.searchAsync(s,{...h,tag:o});u=this.#i(u,l),t?.setAttribute("product",o)}if(a&&a.length){const l=await this.#e.searchAsync(s,{...h,tag:a});u=this.#i(u,l),t?.setAttribute("versions",a.join(","))}for(const l of u)for(const d of l.result){const f=m.get(d)?.fields||[];m.set(d,{fields:l.field?[...f,l.field]:[...f]})}let g=0;for(const[l,d]of m.entries()){if(g>=w)break;const f=this.#t.get(l);f&&!this.#n(f,a)&&(n.push({document:this.#o(f,s,d.fields),highlight:this.#a(f,s,d.fields)}),g++)}return{documents:{[this.id]:n},facets:{}}})}#o(e,t,s){return this.#c(e,t,s)}#a(e,t,s){const r={};let i=!1;for(const o of s)if(o==="path")r.path=e.path?e.path.map(a=>p(t,a)):[];else if(o.includes("parameters")){if(!i){const a=o.split(":")[1],n=e.parameters?.find(c=>{const h=c[a];return this.#r(t,typeof h=="boolean"?h.toString():h)});n&&(r.parameters=[{name:p(t,n.name),description:p(t,n.description),place:p(t,n.place),path:n.path?n.path.map(c=>p(t,c)):[]}],i=!0)}}else r[o]=p(t,e[o]);return r}#r=(e,t)=>{if(t){const s=e.split(/\s+/g);return Array.isArray(t)?t.some(r=>s.some(i=>r.toLowerCase().includes(i.toLowerCase()))):s.some(r=>t.toLowerCase().includes(r.toLowerCase()))}else return!1};#i(e,t){const s=[];for(const r of e){const i=[];for(const o of t)for(const a of r.result){const n=o.result.find(c=>c===a);n&&i.push(n)}i.length&&s.push({result:i,field:r.field})}return s}#n(e,t){return!!(t&&t.length&&e.isDefaultVersion&&e.versionFolderId&&e.version&&t.some(s=>s.includes(e.versionFolderId??""))&&!t.some(s=>s.includes(e.version??"")))}#c(e,t,s){for(const i of L)if(s.some(o=>o===i))return e;let r;for(const i of s)if(i.includes("parameters")){const o=i.split(":")[1],a=e.parameters?.find(n=>{const c=n[o];return this.#r(t,typeof c=="boolean"?c.toString():c)});if(a){r=a.deepLink;break}}if(r){const i=r.split("#")[1];return{...e,url:`${e.url}#${i}`}}return e}}export{y as FlexSearchIndex};
1
+ import S from"flexsearch";import{REDOCLY_TEAMS_RBAC as I}from"@redocly/config";import{DISABLE_DEEP_LINK_IF_FIELDS_EXIST as w,SEARCH_LIMIT as y}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as A}from"../../../../telemetry/helpers/trace-step.js";import{highlightTextForSearch as u}from"../../../../utils/search/highlight-text-for-search.js";class C{#e;#t=new Map;#s=1;id;constructor(e,t){this.id=e,this.#e=new S.Document(t)}get documents(){return this.#t.entries()}get documentsCount(){return this.#s}clearDocuments(){this.#t.clear()}async export(e){await this.#e.export(e)}async import(e){const{documents:t,index:s}=e;this.#t.clear();for(const[r,i]of t)this.#t.set(r,i);for(const[r,i]of Object.entries(s))await this.#e.import(r,i)}add(e){e.tags||(e.tags=[]),this.#t.set(this.#s,e[I]?e:{...e,[I]:void 0}),this.#e.add(this.#s,e),this.#s++}async search(e){return await A("com.redocly.search.performed",async t=>{t?.setAttribute("id","searchQueryId"),t?.setAttribute("object","searchQuery"),t?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:s,offset:r,auth:i,product:o,versions:a}=e,n=[],c=i.teams,l={limit:y,offset:r};let m=new Map,d=await this.#e.searchAsync(s,{...l,tag:c});if(o){const h=await this.#e.searchAsync(s,{...l,tag:o});d=this.#i(d,h),t?.setAttribute("product",o)}if(a&&a.length){const h=await this.#e.searchAsync(s,{...l,tag:a});d=this.#i(d,h),t?.setAttribute("versions",a.join(","))}for(const h of d)for(const p of h.result){const f=m.get(p)?.fields||[];m.set(p,{fields:h.field?[...f,h.field]:[...f]})}let g=0;for(const[h,p]of m.entries()){if(g>=y)break;const f=this.#t.get(h);f&&!this.#n(f,a)&&(n.push({document:this.#o(f,s,p.fields),highlight:this.#a(f,s,p.fields)}),g++)}return{documents:{[this.id]:n},facets:{}}})}#o(e,t,s){return this.#c(e,t,s)}#a(e,t,s){const r={};let i=!1;for(const o of s)if(o==="path")r.path=e.path?e.path.map(a=>u(t,a)):[];else if(o.includes("parameters")){if(!i){const a=o.split(":")[1],n=e.parameters?.find(c=>{const l=c[a];return this.#r(t,typeof l=="boolean"?l.toString():l)});n&&(r.parameters=[{name:u(t,n.name),description:u(t,n.description),place:u(t,n.place),path:n.path?n.path.map(c=>u(t,c)):[]}],i=!0)}}else r[o]=u(t,e[o]);return r}#r=(e,t)=>{if(t){const s=e.split(/\s+/g);return Array.isArray(t)?t.some(r=>s.some(i=>r.toLowerCase().includes(i.toLowerCase()))):s.some(r=>t.toLowerCase().includes(r.toLowerCase()))}else return!1};#i(e,t){const s=[];for(const r of e){const i=[];for(const o of t)for(const a of r.result){const n=o.result.find(c=>c===a);n&&i.push(n)}i.length&&s.push({result:i,field:r.field})}return s}#n(e,t){return!!(t&&t.length&&e.isDefaultVersion&&e.versionFolderId&&e.version&&t.some(s=>s.includes(e.versionFolderId??""))&&!t.some(s=>s.includes(e.version??"")))}#c(e,t,s){for(const i of w)if(s.some(o=>o===i))return e;let r;for(const i of s)if(i.includes("parameters")){const o=i.split(":")[1],a=e.parameters?.find(n=>{const c=n[o];return this.#r(t,typeof c=="boolean"?c.toString():c)});if(a){r=a.deepLink;break}}if(r){const i=r.split("#")[1];return{...e,url:`${e.url}#${i}`}}return e}}export{C as FlexSearchIndex};
@@ -1 +1 @@
1
- import{appendFile as g}from"node:fs/promises";import{existsSync as I}from"node:fs";import m from"node:path";import{Client as $}from"typesense";import{SEARCH_GROUP_FACET_FIELD as E,SEARCH_PRODUCT_FIELD as R,SEARCH_RBAC_FIELD as A,SEARCH_VERSION_FIELD as f}from"../../../../../constants/common.js";import{BASE_SEARCH_DOCUMENT as T,DISABLE_DEEP_LINK_IF_FIELDS_EXIST as O,HIGHLIGHTED_TEXT_MAX_LENGTH as b,SEARCH_DATA_EXPORT_FOLDER as F,SEARCH_DOCUMENT_METADATA_KEY as P,SEARCH_GROUP_LIMIT as N,SEARCH_MAX_FACET_VALUES as S,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as C}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as D}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as p}from"../../../../config/env-config.js";import{ensureDir as d}from"../../../../utils/index.js";class U{#e=null;#r="";#t=[{name:"title",type:"string",facet:!1,optional:!0},{name:"text",type:"string",facet:!1,optional:!0},{name:"path",type:"string[]",facet:!1,optional:!0},{name:"isAdditionalOperation",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"deprecated",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"parameters",type:"object[]",facet:!1,optional:!0},{name:R,type:"string",facet:!0,optional:!0},{name:f,type:"object",facet:!0,optional:!0},{name:A,type:"string[]",facet:!0},{name:"metadata_curated",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"metadata_keywords_excludes",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:"metadata_keywords_includes",type:"string[]",facet:!1,optional:!0,queryable:!1}];#s=new Map;#n={ORGANIZATION_ID:p.ORGANIZATION_ID??"",PROJECT_ID:p.PROJECT_ID??"",TYPESENSE_API_URL:p.TYPESENSE_API_URL??"",TYPESENSE_API_KEY:p.TYPESENSE_API_KEY??""};constructor(){if(!p.isBuildMode){for(const[e,t]of Object.entries(this.#n))if(!t)throw new Error(`Cannot initialize search index. ${e} is not set`);this.#r=`${this.#n.ORGANIZATION_ID}_${this.#n.PROJECT_ID}_`,this.#e=new $({nodes:[{url:this.#n.TYPESENSE_API_URL}],apiKey:this.#n.TYPESENSE_API_KEY,connectionTimeoutSeconds:5})}}async initIndexSchema(e){this.#p(e)}async addDocuments(e,t){e.push(T);const{locale:n,outDir:o}=t,s=e.map(i=>{const r={...i},c=i[P];return c&&(c.curated&&(r.metadata_curated=c.curated),c.excludes&&(r.metadata_keywords_excludes=c.excludes),c.includes&&(r.metadata_keywords_includes=c.includes)),r}),a=this.#s.get(n)??[];this.#s.set(n,a.concat(s)),a.length+e.length>=C&&await this.exportDocuments(o)}async search(e,t){return await D("search",async n=>{const{query:o,locale:s,filter:a,loadMore:i}=e,r=this.#c(s),c={collection:r,q:o||"*",query_by:this.#l(),max_facet_values:S};if(n?.setAttribute("collectionName",r),n?.setAttribute("locale",s),this.#e)if(i){const{groupKey:l,offset:u}=i,h={offset:u,filter_by:this.#o(t,a),facet_by:"*"};return n?.setAttribute("isLoadMore",!0),n?.setAttribute("filter",h.filter_by),this.#u(await this.#e.collections(r).documents().search({...c,...h}),l)}else{const l={group_by:this.#m(),group_limit:N,filter_by:this.#o(t,a)};n?.setAttribute("groups",l.group_by),n?.setAttribute("filter",l.filter_by);const u=[l,...this.#y(t,a)];return this.#f(await this.#e?.multiSearch.perform({searches:u},c),a)}else return{facets:{},documents:{}}})}#u(e,t){const n={facets:{},documents:{[t]:[]}};if(e.hits&&e.hits.length)for(const o of e.hits)n.documents[t].push({document:this.#a(o),highlight:this.#i(o)});return n}#f(e,t){const n={facets:{},documents:{}};for(const o of this.#_(t))n.documents[o]=[];if("results"in e)for(const o of e.results){if(o.facet_counts&&o.facet_counts.length)for(const s of o.facet_counts)n.facets[s.field_name]=s.counts.map(a=>({value:a.value,count:a.count}));if(o.grouped_hits&&o.grouped_hits.length)for(const s of o.grouped_hits){const a=s.group_key[0],i=[];for(const r of s.hits)i.push({document:this.#a(r),highlight:this.#i(r)});n.documents[a]=i}}return n}#a(e){return this.#h(e)}#i(e){const t={},n=[];for(const[o,s]of Object.entries(e.highlight??{}))if(Array.isArray(s)){if(o==="path"){const a=s,i=[];for(const r of a)i.push(r.snippet);t.path=i}else if(o==="parameters"){const a=s;for(const i of a){let r=!1;for(const[c,l]of Object.entries(i))if(c!=="deepLink")if(Array.isArray(l))for(const u of l)u.matched_tokens?.length&&(r=!0);else l.matched_tokens?.length&&(r=!0);if(r){const c={name:i.name?.snippet||"",description:i.description?.snippet||"",place:i.place?.snippet||"",path:i.path?.map(l=>l?.snippet)||[]};t.parameters=[c];break}}}}else t[o]=s.snippet,n.push(o);for(const[o,s]of Object.entries(e.document))!n.includes(o)&&typeof s=="string"&&(t[o]=s.length>b?`${s.substring(0,b)}...`:s);return t.parameters||(t.parameters=[]),t}#h(e){let t;const n=Object.keys(e.highlight);for(const s of O)if(n.some(a=>a===s))return e.document;const o=e.highlight.parameters;if(o){for(const s of o)for(const[a,i]of Object.entries(s))if(a!=="deepLink"){if(Array.isArray(i)){for(const r of i)if(r.matched_tokens?.length){t=s.deepLink.snippet;break}}else if(i.matched_tokens?.length){t=s.deepLink.snippet;break}}}if(t){const s=t.split("#")[1];return{...e.document,url:`${e.document.url}#${s}`}}return e.document}async exportDocuments(e){const t='{"documents":[',n=d(m.join(e,F));for(const[o,s]of this.#s){if(s.length===0)continue;const a=d(m.join(n,`${o}.json`)),i=!I(a),r=JSON.stringify(s).substring(1).slice(0,-1),c=i?t+r:","+r;await g(a,c,{encoding:"utf8"}),this.#s.set(o,[])}}async exportIndexes(e){const t=d(m.join(e,F));for(const n of this.#s.keys()){const o=d(m.join(t,`${n}.json`)),s=`],"schemaFields":${JSON.stringify(this.#t)}}`;await g(o,s,{encoding:"utf8"})}}async import(e){}async countFacets(e,t){return await D("search.facets",async n=>{const{locale:o,query:s,facetQuery:a,filter:i,field:r}=e,c=this.#c(o),l={q:s||"*",query_by:this.#l(),facet_by:"*",facet_query:this.#d(a,r),filter_by:this.#o(t,i,r),max_facet_values:S};if(n?.setAttribute("collectionName",c),n?.setAttribute("query",l.q),n?.setAttribute("facetQuery",l.facet_query),n?.setAttribute("filter",l.filter_by),this.#e){const u={},h=await this.#e.collections(c).documents().search(l);if(h.facet_counts&&h.facet_counts.length)for(const _ of h.facet_counts)u[_.field_name]=_.counts.map(y=>({value:y.value,count:y.count,isCounterVisible:!!s||(i?.length||0)>0}));return u}else return{}})}#c(e){return`${this.#r}${e}`}#p(e){for(const[t,{queryable:n}]of e)this.#t.find(o=>o.name===t)||this.#t.push({queryable:n,name:t,type:"string",facet:!0,optional:!0})}#l(){return this.#t.filter(e=>e.facet?e.queryable:e.queryable!==!1).map(e=>e.name).join(",")}#m(){return this.#t.filter(e=>e.facet&&e.name===E).map(e=>e.name).join(",")}#o(e,t,n){const o=`${A}:[${e.teams.map(a=>`'${a}'`).join(", ")}]`;let s=`${f}.isDefault:true`;if(t&&t.length){const a=t.filter(r=>r.field!==n&&r.field!==f).map(r=>{const c=r.values.map(l=>`'${l}'`);return c.length?`${r.field}:=[${c.join(", ")}]`:""}).join(" && "),i=t.find(r=>r.field===f);if(i&&i.values.length&&i.values.length===2){const r=i.values[0],c=i.values[1],l=`(${f}.folderId:! ${r} && ${f}.isDefault:true)`,u=`(${f}.folderId: ${r} && ${f}.version:='${c}')`;s=`(${l} || ${u})`}return a?`${o} && ${a} && ${s}`:`${o} && ${s}`}else return`${o} && ${s}`}#d(e,t){return e&&t?`${t}:${e}`:""}#_(e){const t=[];if(e&&e.length)for(const n of e)n.field===E&&t.push(...n.values);return t}#y(e,t){const n=[],o=this.#t.filter(s=>s.facet);for(const s of o){const a={facet_by:s.name,filter_by:this.#o(e,t,s.name)};n.push(a)}return n}cleanupFacetValues(e){const t=e.getSearchFacets();t.forEach(n=>{n.values=[]}),e.setSearchFacets(t)}}export{U as Typesense};
1
+ import{appendFile as g}from"node:fs/promises";import{existsSync as $}from"node:fs";import m from"node:path";import{Client as R}from"typesense";import{SEARCH_GROUP_FACET_FIELD as E,SEARCH_PRODUCT_FIELD as S,SEARCH_RBAC_FIELD as b,SEARCH_VERSION_FIELD as f}from"../../../../../constants/common.js";import{BASE_SEARCH_DOCUMENT as T,DISABLE_DEEP_LINK_IF_FIELDS_EXIST as O,HIGHLIGHTED_TEXT_MAX_LENGTH as A,SEARCH_DATA_EXPORT_FOLDER as F,SEARCH_DOCUMENT_METADATA_KEY as P,SEARCH_GROUP_LIMIT as N,SEARCH_MAX_FACET_VALUES as I,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as C}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as D}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as p}from"../../../../config/env-config.js";import{ensureDir as d}from"../../../../utils/index.js";class U{#e=null;#n="";#t=[{name:"title",type:"string",facet:!1,optional:!0},{name:"text",type:"string",facet:!1,optional:!0},{name:"path",type:"string[]",facet:!1,optional:!0},{name:"isAdditionalOperation",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"deprecated",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"parameters",type:"object[]",facet:!1,optional:!0},{name:S,type:"string",facet:!0,optional:!0},{name:f,type:"object",facet:!0,optional:!0},{name:b,type:"string[]",facet:!0},{name:"metadata_curated",type:"bool",facet:!1,optional:!0,queryable:!1},{name:"metadata_keywords_excludes",type:"string[]",facet:!1,optional:!0,queryable:!1},{name:"metadata_keywords_includes",type:"string[]",facet:!1,optional:!0,queryable:!1}];#s=new Map;#r={ORGANIZATION_ID:p.ORGANIZATION_ID??"",PROJECT_ID:p.PROJECT_ID??"",TYPESENSE_API_URL:p.TYPESENSE_API_URL??"",TYPESENSE_API_KEY:p.TYPESENSE_API_KEY??""};constructor(){if(!p.isBuildMode){for(const[e,t]of Object.entries(this.#r))if(!t)throw new Error(`Cannot initialize search index. ${e} is not set`);this.#n=`${this.#r.ORGANIZATION_ID}_${this.#r.PROJECT_ID}_`,this.#e=new R({nodes:[{url:this.#r.TYPESENSE_API_URL}],apiKey:this.#r.TYPESENSE_API_KEY,connectionTimeoutSeconds:5})}}async initIndexSchema(e){this.#p(e)}async addDocuments(e,t){e.push(T);const{locale:s,outDir:o}=t,r=e.map(i=>{const n={...i},c=i[P];return c&&(c.curated&&(n.metadata_curated=c.curated),c.excludes&&(n.metadata_keywords_excludes=c.excludes),c.includes&&(n.metadata_keywords_includes=c.includes)),n}),a=this.#s.get(s)??[];this.#s.set(s,a.concat(r)),a.length+e.length>=C&&await this.exportDocuments(o)}async search(e,t){return await D("com.redocly.search.performed",async s=>{s?.setAttribute("id","searchQueryId"),s?.setAttribute("object","searchQuery"),s?.setAttribute("uri","urn:redocly:realm:api:searchQuery:searchQueryId");const{query:o,locale:r,filter:a,loadMore:i}=e,n=this.#c(r),c={collection:n,q:o||"*",query_by:this.#l(),max_facet_values:I};if(s?.setAttribute("collectionName",n),s?.setAttribute("locale",r),this.#e)if(i){const{groupKey:l,offset:u}=i,h={offset:u,filter_by:this.#o(t,a),facet_by:"*"};return s?.setAttribute("isLoadMore",!0),s?.setAttribute("filter",h.filter_by),this.#u(await this.#e.collections(n).documents().search({...c,...h}),l)}else{const l={group_by:this.#m(),group_limit:N,filter_by:this.#o(t,a)};s?.setAttribute("groups",l.group_by),s?.setAttribute("filter",l.filter_by);const u=[l,...this.#y(t,a)];return this.#f(await this.#e?.multiSearch.perform({searches:u},c),a)}else return{facets:{},documents:{}}})}#u(e,t){const s={facets:{},documents:{[t]:[]}};if(e.hits&&e.hits.length)for(const o of e.hits)s.documents[t].push({document:this.#a(o),highlight:this.#i(o)});return s}#f(e,t){const s={facets:{},documents:{}};for(const o of this.#_(t))s.documents[o]=[];if("results"in e)for(const o of e.results){if(o.facet_counts&&o.facet_counts.length)for(const r of o.facet_counts)s.facets[r.field_name]=r.counts.map(a=>({value:a.value,count:a.count}));if(o.grouped_hits&&o.grouped_hits.length)for(const r of o.grouped_hits){const a=r.group_key[0],i=[];for(const n of r.hits)i.push({document:this.#a(n),highlight:this.#i(n)});s.documents[a]=i}}return s}#a(e){return this.#h(e)}#i(e){const t={},s=[];for(const[o,r]of Object.entries(e.highlight??{}))if(Array.isArray(r)){if(o==="path"){const a=r,i=[];for(const n of a)i.push(n.snippet);t.path=i}else if(o==="parameters"){const a=r;for(const i of a){let n=!1;for(const[c,l]of Object.entries(i))if(c!=="deepLink")if(Array.isArray(l))for(const u of l)u.matched_tokens?.length&&(n=!0);else l.matched_tokens?.length&&(n=!0);if(n){const c={name:i.name?.snippet||"",description:i.description?.snippet||"",place:i.place?.snippet||"",path:i.path?.map(l=>l?.snippet)||[]};t.parameters=[c];break}}}}else t[o]=r.snippet,s.push(o);for(const[o,r]of Object.entries(e.document))!s.includes(o)&&typeof r=="string"&&(t[o]=r.length>A?`${r.substring(0,A)}...`:r);return t.parameters||(t.parameters=[]),t}#h(e){let t;const s=Object.keys(e.highlight??{});for(const r of O)if(s.some(a=>a===r))return e.document;const o=e.highlight?.parameters;if(o){for(const r of o)for(const[a,i]of Object.entries(r))if(a!=="deepLink"){if(Array.isArray(i)){for(const n of i)if(n.matched_tokens?.length){t=r.deepLink?.snippet??t;break}}else if(i.matched_tokens?.length){t=r.deepLink?.snippet??t;break}}}if(t){const r=t.split("#")[1];return{...e.document,url:`${e.document.url}#${r}`}}return e.document}async exportDocuments(e){const t='{"documents":[',s=d(m.join(e,F));for(const[o,r]of this.#s){if(r.length===0)continue;const a=d(m.join(s,`${o}.json`)),i=!$(a),n=JSON.stringify(r).substring(1).slice(0,-1),c=i?t+n:","+n;await g(a,c,{encoding:"utf8"}),this.#s.set(o,[])}}async exportIndexes(e){const t=d(m.join(e,F));for(const s of this.#s.keys()){const o=d(m.join(t,`${s}.json`)),r=`],"schemaFields":${JSON.stringify(this.#t)}}`;await g(o,r,{encoding:"utf8"})}}async import(e){}async countFacets(e,t){return await D("com.redocly.search.facets",async s=>{s?.setAttribute("id","searchFacetsId"),s?.setAttribute("object","searchFacets"),s?.setAttribute("uri","urn:redocly:realm:api:searchFacets:searchFacetsId");const{locale:o,query:r,facetQuery:a,filter:i,field:n}=e,c=this.#c(o),l={q:r||"*",query_by:this.#l(),facet_by:"*",facet_query:this.#d(a,n),filter_by:this.#o(t,i,n),max_facet_values:I};if(s?.setAttribute("collectionName",c),s?.setAttribute("query",l.q),s?.setAttribute("facetQuery",l.facet_query),s?.setAttribute("filter",l.filter_by),this.#e){const u={},h=await this.#e.collections(c).documents().search(l);if(h.facet_counts&&h.facet_counts.length)for(const _ of h.facet_counts)u[_.field_name]=_.counts.map(y=>({value:y.value,count:y.count,isCounterVisible:!!r||(i?.length||0)>0}));return u}else return{}})}#c(e){return`${this.#n}${e}`}#p(e){for(const[t,{queryable:s}]of e)this.#t.find(o=>o.name===t)||this.#t.push({queryable:s,name:t,type:"string",facet:!0,optional:!0})}#l(){return this.#t.filter(e=>e.facet?e.queryable:e.queryable!==!1).map(e=>e.name).join(",")}#m(){return this.#t.filter(e=>e.facet&&e.name===E).map(e=>e.name).join(",")}#o(e,t,s){const o=`${b}:[${e.teams.map(a=>`'${a}'`).join(", ")}]`;let r=`${f}.isDefault:true`;if(t&&t.length){const a=t.filter(n=>n.field!==s&&n.field!==f).map(n=>{const c=n.values.map(l=>`'${l}'`);return c.length?`${n.field}:=[${c.join(", ")}]`:""}).join(" && "),i=t.find(n=>n.field===f);if(i&&i.values.length&&i.values.length===2){const n=i.values[0],c=i.values[1],l=`(${f}.folderId:! ${n} && ${f}.isDefault:true)`,u=`(${f}.folderId: ${n} && ${f}.version:='${c}')`;r=`(${l} || ${u})`}return a?`${o} && ${a} && ${r}`:`${o} && ${r}`}else return`${o} && ${r}`}#d(e,t){return e&&t?`${t}:${e}`:""}#_(e){const t=[];if(e&&e.length)for(const s of e)s.field===E&&t.push(...s.values);return t}#y(e,t){const s=[],o=this.#t.filter(r=>r.facet);for(const r of o){const a={facet_by:r.name,filter_by:this.#o(e,t,r.name)};s.push(a)}return s}cleanupFacetValues(e){const t=e.getSearchFacets();t.forEach(s=>{s.values=[]}),e.setSearchFacets(t)}}export{U as Typesense};
@@ -1,2 +1,2 @@
1
1
  import{telemetry as c}from"../../telemetry/index.js";import{envConfig as l}from"../../config/env-config.js";import h,{LogLevel as r,shouldLog as m,parseLogLevel as u}from"./formatter.js";import{isVirtualFile as f}from"../../fs/utils/isVirtualFile.js";import{TerminalManager as g}from"./terminal-manager.js";class p{#e;#l;#i;#n;#r=new Map;#o=new Map;constructor({context:t,allowInteractive:e,minLogLevel:i}={}){this.#e=t,this.#l=l.isProductionEnv,this.#i=new g(e),this.#n=i??u(l.REDOCLY_LOG_LEVEL)??(l.isDevelopMode?r.INFO:r.HTTP)}shouldLog(t){return m(t,this.#n)}info(t,...e){this.#t({level:r.INFO,message:t,args:e})}infoTime(t,e,...i){return this.#s(r.INFO,e,t,...i)}success(t,...e){this.#t({level:r.SUCCESS,message:t,args:e})}logInFooter(t,e,...i){const o=h.interpolate(e,...i)+`
2
- `;this.isInteractive()||this.#i.isFooterChanged(t,o)&&this.#t({level:r.INFO,message:e,args:i}),this.#i.updateFooter(t,o)}successTime(t,e,...i){return this.#s(r.SUCCESS,e,t,...i)}warn(t,...e){this.#t({level:r.WARN,message:t,args:e})}warnProd(t,...e){this.#l?this.warn(t,...e):this.verbose(t,...e)}error(t,...e){this.#t({level:r.ERROR,message:t,args:e})}contentError(t,...e){this.#t({level:r.ERROR,message:t,scope:"content",args:e})}verbose(t,...e){this.#t({level:r.VERBOSE,message:t,args:e})}verboseTime(t,e,...i){return this.#s(r.VERBOSE,e,t,...i)}httpTime(t){return this.#s(r.HTTP,"",t)}startTiming(t){const e=t||Symbol();this.#r.set(e,performance.now());const i=setTimeout(()=>{this.#r.delete(e),this.#o.delete(e)},500*1e3);return this.#o.set(e,i),e}updateContext(t){this.#e={...this.#e,...t}}clearAllTimeouts(){for(const t of this.#o.values())clearTimeout(t);this.#o.clear()}isInteractive(){return this.#i.isInteractive()}warnForRealFile(t,e,i,...o){f(e,i)||this.warn(t,e,...o)}#s(t,e,i,...o){const n=this.#r.get(i);if(!n)return;const s=Math.round(performance.now()-n);return this.#r.delete(i),this.#t({level:t,message:e,duration:s,args:o}),{message:e,timeMs:s}}#t({level:t,message:e,duration:i,scope:o,args:n}){if(!m(t,this.#n))return;let s=e&&h.interpolate(e,...n);s&&i!=null&&t!==r.VERBOSE&&c.sendTimingPerformedMessage([{object:"timing",timeMs:i,message:s}]);const a={level:t,message:s,duration:i,scope:o,context:this.#e};process.stderr.write(h.format(a))}}const R=new p({allowInteractive:!0});export{p as Logger,R as logger};
2
+ `;this.isInteractive()||this.#i.isFooterChanged(t,o)&&this.#t({level:r.INFO,message:e,args:i}),this.#i.updateFooter(t,o)}successTime(t,e,...i){return this.#s(r.SUCCESS,e,t,...i)}warn(t,...e){this.#t({level:r.WARN,message:t,args:e})}warnProd(t,...e){this.#l?this.warn(t,...e):this.verbose(t,...e)}error(t,...e){this.#t({level:r.ERROR,message:t,args:e})}contentError(t,...e){this.#t({level:r.ERROR,message:t,scope:"content",args:e})}verbose(t,...e){this.#t({level:r.VERBOSE,message:t,args:e})}verboseTime(t,e,...i){return this.#s(r.VERBOSE,e,t,...i)}httpTime(t){return this.#s(r.HTTP,"",t)}startTiming(t){const e=t||Symbol();this.#r.set(e,performance.now());const i=setTimeout(()=>{this.#r.delete(e),this.#o.delete(e)},500*1e3);return this.#o.set(e,i),e}updateContext(t){this.#e={...this.#e,...t}}clearAllTimeouts(){for(const t of this.#o.values())clearTimeout(t);this.#o.clear()}isInteractive(){return this.#i.isInteractive()}warnForRealFile(t,e,i,...o){f(e,i)||this.warn(t,e,...o)}#s(t,e,i,...o){const n=this.#r.get(i);if(!n)return;const s=Math.round(performance.now()-n);return this.#r.delete(i),this.#t({level:t,message:e,duration:s,args:o}),{message:e,timeMs:s}}#t({level:t,message:e,duration:i,scope:o,args:n}){if(!m(t,this.#n))return;let s=e&&h.interpolate(e,...n);s&&i!=null&&t!==r.VERBOSE&&c.sendTimingPerformedMessage([{id:"timingId",object:"timing",uri:"urn:redocly:realm:api:timing:timingId",timeMs:i,message:s}]);const a={level:t,message:s,duration:i,scope:o,context:this.#e};process.stderr.write(h.format(a))}}const R=new p({allowInteractive:!0});export{p as Logger,R as logger};
@@ -1,11 +1,11 @@
1
- import{envConfig as h}from"../../config/env-config.js";import{logger as e}from"../../tools/notifiers/logger.js";import P from"./formatter.js";import{blue as g,gray as p,red as a}from"./helpers/colors.js";import{isVirtualFile as C}from"../../fs/utils/isVirtualFile.js";import{telemetry as u}from"../../telemetry/index.js";import{shutdowner as f}from"../shutdowner.js";const c=30;class b{#r=[];#o=[];#e=new Map;#t=[];pushError(r,t,...o){const n=P.interpolate(r,...o);this.#r.push({severity:t,message:n,type:"ERROR"})}reportBrokenLink(r){this.#r.push(r)}reportCompilationError(r){const t=`${r.message}::${r.sourceFileRelativePath}::${r?.sourceFileLocation?.line}`;this.#e.has(t)||this.#e.set(t,r)}reportPageRenderError(r){this.#t.push(r)}async panicOnBuild(r,...t){r instanceof Error&&(r=r.message+`
2
- `+r.stack),h.isBuildMode?await this.panic(r,...t):this.pushError(r,"PANIC",...t)}async panicOnBuildContentErrorForRealFile(r,t,o,...n){C(t,o)||await this.panicOnBuildContentError(r,...n)}async panicOnBuildContentError(r,...t){r instanceof Error&&(r=r.message+`
1
+ import{envConfig as h}from"../../config/env-config.js";import{logger as e}from"../../tools/notifiers/logger.js";import P from"./formatter.js";import{blue as g,gray as p,red as a}from"./helpers/colors.js";import{isVirtualFile as C}from"../../fs/utils/isVirtualFile.js";import{telemetry as u}from"../../telemetry/index.js";import{shutdowner as d}from"../shutdowner.js";const c=30;class b{#r=[];#o=[];#e=new Map;#t=[];pushError(r,t,...o){const i=P.interpolate(r,...o);this.#r.push({severity:t,message:i,type:"ERROR"})}reportBrokenLink(r){this.#r.push(r)}reportCompilationError(r){const t=`${r.message}::${r.sourceFileRelativePath}::${r?.sourceFileLocation?.line}`;this.#e.has(t)||this.#e.set(t,r)}reportPageRenderError(r){this.#t.push(r)}async panicOnBuild(r,...t){r instanceof Error&&(r=r.message+`
2
+ `+r.stack),h.isBuildMode?await this.panic(r,...t):this.pushError(r,"PANIC",...t)}async panicOnBuildContentErrorForRealFile(r,t,o,...i){C(t,o)||await this.panicOnBuildContentError(r,...i)}async panicOnBuildContentError(r,...t){r instanceof Error&&(r=r.message+`
3
3
  `+r.stack),h.isBuildMode?await this.panicOnContentError(r,...t):this.pushError(r,"PANIC",...t)}async panic(r,...t){let o;r instanceof Error?(o=r,r=r.message+`
4
- `+r.stack):o=new Error(r),e.error(r,...t),u.sendCliErrorCaughtMessage([{object:"cli",message:r}]),await f.exitWithCode(1,o)}async panicOnContentError(r,...t){let o;r instanceof Error?(o=r,r=r.message+`
5
- `+r.stack):o=new Error(r),e.contentError(r,...t),u.sendCliErrorCaughtMessage([{object:"cli",message:r,scope:"content"}]),await f.exitWithCode(1,o)}reportMarkdocProblem(r){this.#o.push(r)}clearErrors(){this.#r=[]}clearMarkdocProblems(){this.#o=[]}clearEsbuildProblems(){this.#e.clear()}clearPageRenderProblems(){this.#t=[]}getCompilationProblem(r){return this.#e.get(r)}getCompilationProblems(){return[...this.#e.values()]}getPageRenderProblems(){return this.#t}getPageRenderProblem(r){return this.#t.find(t=>t.sourceFileRelativePath===r)}getProblems(){return[...this.#r,...this.getCompilationProblems(),...this.#o,...this.#t]}summary(r,t=0){const o=this.getProblems(),n=this.#o.filter(s=>s.type==="BROKEN_LINK").length,l=this.#r.filter(s=>s.type==="BROKEN_LINK").length,d=this.#o.length-n,E=this.#r.length-l+this.getCompilationProblems().length+this.#t.length;return e.logInFooter("validate",o.length?a(" \u274C Status: %s markdoc errors, %s broken links, %s other errors"):" \u2705 Status: No errors found",d,n+l,E),t!==0&&e.logInFooter("pages",` \u{1F4C4} Total pages: ${t}`),e.logInFooter("timing",r),e.logInFooter("validate-sep",""),e.isInteractive()&&(o.length?e.logInFooter("actions","Press (e) to print all errors, (q) to quit"):e.logInFooter("actions",p("Press (q) to quit"))),o}printErrors(r=[]){const t=this.#r.length,o=r.length?r:this.getProblems();let n=0;for(const l of o.slice(0,c))n>=t?e.contentError(a(`[${++n}] `)+m(l)):e.error(a(`[${++n}] `)+m(l));o.length>c&&e.error(`... and ${o.length-c} more errors`)}}const $=new b;function m(i){const r=i.sourceFileLocation,t=r?`:${r.line}:${r.character??1}`:"";return i.message+(i.codeframe?`
4
+ `+r.stack):o=new Error(r),e.error(r,...t),u.sendCliErrorCaughtMessage([{id:"cliId",object:"cli",uri:"urn:redocly:realm:api:cli:cliId",message:r}]),await d.exitWithCode(1,o)}async panicOnContentError(r,...t){let o;r instanceof Error?(o=r,r=r.message+`
5
+ `+r.stack):o=new Error(r),e.contentError(r,...t),u.sendCliErrorCaughtMessage([{id:"cliId",object:"cli",uri:"urn:redocly:realm:api:cli:cliId",message:r,scope:"content"}]),await d.exitWithCode(1,o)}reportMarkdocProblem(r){this.#o.push(r)}clearErrors(){this.#r=[]}clearMarkdocProblems(){this.#o=[]}clearEsbuildProblems(){this.#e.clear()}clearPageRenderProblems(){this.#t=[]}getCompilationProblem(r){return this.#e.get(r)}getCompilationProblems(){return[...this.#e.values()]}getPageRenderProblems(){return this.#t}getPageRenderProblem(r){return this.#t.find(t=>t.sourceFileRelativePath===r)}getProblems(){return[...this.#r,...this.getCompilationProblems(),...this.#o,...this.#t]}summary(r,t=0){const o=this.getProblems(),i=this.#o.filter(s=>s.type==="BROKEN_LINK").length,l=this.#r.filter(s=>s.type==="BROKEN_LINK").length,f=this.#o.length-i,E=this.#r.length-l+this.getCompilationProblems().length+this.#t.length;return e.logInFooter("validate",o.length?a(" \u274C Status: %s markdoc errors, %s broken links, %s other errors"):" \u2705 Status: No errors found",f,i+l,E),t!==0&&e.logInFooter("pages",` \u{1F4C4} Total pages: ${t}`),e.logInFooter("timing",r),e.logInFooter("validate-sep",""),e.isInteractive()&&(o.length?e.logInFooter("actions","Press (e) to print all errors, (q) to quit"):e.logInFooter("actions",p("Press (q) to quit"))),o}printErrors(r=[]){const t=this.#r.length,o=r.length?r:this.getProblems();let i=0;for(const l of o.slice(0,c))i>=t?e.contentError(a(`[${++i}] `)+m(l)):e.error(a(`[${++i}] `)+m(l));o.length>c&&e.error(`... and ${o.length-c} more errors`)}}const $=new b;function m(n){const r=n.sourceFileLocation,t=r?`:${r.line}:${r.character??1}`:"";return n.message+(n.codeframe?`
6
6
 
7
- `+i.codeframe+`
7
+ `+n.codeframe+`
8
8
 
9
9
  `:`
10
- `)+(i.sourceFileRelativePath?p(`at ${g("./"+i.sourceFileRelativePath)}${t}`)+`
10
+ `)+(n.sourceFileRelativePath?p(`at ${g("./"+n.sourceFileRelativePath)}${t}`)+`
11
11
  `:"")}export{b as Reporter,$ as reporter};
@@ -16,7 +16,7 @@ import type { Logger } from '../../tools/notifiers/logger.js';
16
16
  import type { AiDocumentsStore } from '../../plugins/search/types';
17
17
  import type { ParseMarkdocOpts } from '../../types/plugins/markdown';
18
18
  import type { MarkdocConfigProps } from '../../plugins/markdown/compiler';
19
- import type { ToolAnnotations } from '@redocly/mcp-typescript-sdk/types.js';
19
+ import type { ToolAnnotations } from '@modelcontextprotocol/server';
20
20
  import type { Skill } from '../../types/plugins/skills';
21
21
  export type ResolveItemsOptions = {
22
22
  groupCustomSidebars?: boolean;
@@ -1,12 +1,12 @@
1
- import"../node-crypto-polyfill.js";import{DOMParser as D}from"@xmldom/xmldom";import{SignedXml as Y}from"xml-crypto";import G from"xpath";import{deflateSync as Q,inflateSync as X}from"fflate";import{createHash as Z}from"crypto";import{ulid as ee}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as b}from"@redocly/config";import{AUTH_URL as te,JWT_SECRET_KEY as y}from"../constants/common.js";import{envConfig as ne}from"../config/env-config.js";import{decryptClaimValue as re}from"../utils/crypto/decrypt-claim-value.js";import{getPathPrefix as oe,withPathPrefix as se}from"@redocly/theme/core/utils";import{ServerRoutes as P}from"../../constants/api.js";import{AuthCookieNames as _,DEFAULT_AUTHENTICATED_TEAM as ae,REQUIRED_OIDC_SCOPES as v}from"../../constants/common.js";import{appendQueryParams as ie}from"../../utils/url/append-query-params.js";import{parseHttpsUrl as N}from"../../utils/url/parse-https-url.js";import{logger as ce}from"../tools/notifiers/logger.js";import{randomString as ue}from"../utils/crypto/random-string.js";import{randomUUID as U}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as h,JwtTokenExpired as le}from"./jwt/types.js";import*as l from"./jwt/jwt.js";import{parseTeamClaimToArray as de}from"../utils/index.js";import{arrayBufferToBase64 as me,decodeBase64 as j,encodeBase64URL as pe,urlSafeBase64 as V}from"./jwt/encode.js";import{formatSamlCertificate as fe}from"./utils/format-saml-certificate.js";function $(e){return e?.type===u.OIDC}function he(e){return e?.type===u.SAML2}async function nt(e,t){if($(t))return ye(e,t);if(he(t))return we(e,t)}async function ye(e,t){const n=await q(e,t),r=new Set((t.scopes||[]).concat(v)),o=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function we(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}function H(e){return Array.from(new Set((e.scopes||[]).concat(v))).join(" ")}async function B(e,t,n){return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:t.clientId,...t.clientSecret?{client_secret:t.clientSecret}:{},...n}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(r=>r.json())}async function rt(e,t,n,r,o={}){return await B(e,r,{scope:H(r),code:t,redirect_uri:F(n),grant_type:"authorization_code",...o})}async function ot(e,t,n){return await B(e,n,{scope:H(n),grant_type:"refresh_token",refresh_token:t})}function Se(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:s,idpId:a,pkce:d},p,x,f){if(!t||!n||!r||!o)return{loginUrl:void 0};const i=new URL(t),w=f?.redirectUriOverride??`${e}${se(P.OIDC_CALLBACK)}`,g={state:U(),idpId:a,redirectUri:w,redirectTo:p,branch:f?.branchOverride??Ae(e),inviteCode:x,source:f?.sourceOverride??"portal",uiLocales:f?.uiLocales},S={};if(d){const m=V(ue(50)),k=V(Z("sha256").update(m).digest("base64")),I="S256";i.searchParams.append("code_challenge",k),i.searchParams.append("code_challenge_method",I),S.code_verifier={value:m,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:oe()||"/"}}}i.searchParams.append("client_id",n),i.searchParams.append("scope",o),i.searchParams.append("response_type",r),i.searchParams.append("redirect_uri",F(w)),i.searchParams.append("state",pe(JSON.stringify(g))),f?.uiLocales&&i.searchParams.append("ui_locales",f.uiLocales);for(const m in s)s[m]!==void 0&&i.searchParams.append(m,s[m]);return{loginUrl:i.toString(),cookies:S}}function st(e,t,n,r){const o=new URL(e);return o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n),o.toString()}async function at(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return l.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpId?{idp_id:e.idpId}:{},...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:n},y,h.HS256)}async function it(e){await l.verify(e,y,h.HS256);const{payload:t}=l.decode(e);if(t.type!=="mcp_auth_code")throw new Error("Invalid authorization code type");if(!t.client_id||!t.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof t.exp=="number"&&Date.now()>=t.exp*1e3)throw new Error("Authorization code expired");return t}async function ct(e){const t=Math.floor(Date.now()/1e3);return l.sign({type:"mcp_refresh_token",client_id:e.clientId,...e.idpId?{idp_id:e.idpId}:{},claims:e.claims,...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},session_exp:e.sessionExp,iat:t,exp:e.sessionExp},y,h.HS256)}async function ut(e){await l.verify(e,y,h.HS256);const{payload:t}=l.decode(e);if(t.type!=="mcp_refresh_token")throw new Error("Invalid refresh token type");if(!t.client_id||typeof t.claims!="object"||t.claims===null)throw new Error("Refresh token missing required claims");if(typeof t.session_exp!="number")throw new Error("Refresh token missing session expiry");return t}function lt(e){const t=e||ee(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcpSession",uri:`urn:redocly:realm:mcp:session:${n}`}}function F(e){const t=N(e);if(!t)return e;const n=_e(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,z(e,t);const r=W(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,z(e,t)):e}function Ae(e){const t=N(e);if(t)return W(t.hostname)?.previewBranch}function z(e,t){return e.replace(/^https:\/\/[^/?#]+/i,`https://${t.host}`)}function W(e){const t=e.split(".preview.",2);if(t.length<2)return null;const[n,r]=t,o=n.indexOf("--");if(o===-1)return null;const s=n.slice(0,o),a=n.slice(o+2);return!s||!a||a.includes(".")?null:{projectSlug:s,previewBranch:a,after:r}}function _e(e){const t=e.indexOf("."),n=t===-1?e:e.slice(0,t);if(!n.startsWith("preview-"))return null;const r=n.indexOf("--");if(r===-1)return null;const o=n.slice(r+2);if(!o)return null;const s=t===-1?"":e.slice(t);return{previewBranch:o,after:s}}function xe(e){return e.type===u.OIDC}function ge(e){return e.type===u.SAML2}function dt(e,t,n,r,o){return xe(e)?Se(t,e,n,r,{uiLocales:o}):ge(e)?ke(t,e,n,r,o):{}}function ke(e,t,n,r,o){const a=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
1
+ import"../node-crypto-polyfill.js";import{DOMParser as M}from"@xmldom/xmldom";import{SignedXml as Y}from"xml-crypto";import G from"xpath";import{deflateSync as Q,inflateSync as X}from"fflate";import{createHash as Z}from"crypto";import{ulid as ee}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as P}from"@redocly/config";import{AUTH_URL as te,JWT_SECRET_KEY as y}from"../constants/common.js";import{envConfig as ne}from"../config/env-config.js";import{decryptClaimValue as re}from"../utils/crypto/decrypt-claim-value.js";import{getPathPrefix as oe,withPathPrefix as se}from"@redocly/theme/core/utils";import{ServerRoutes as b}from"../../constants/api.js";import{AuthCookieNames as A,DEFAULT_AUTHENTICATED_TEAM as ae,REQUIRED_OIDC_SCOPES as v}from"../../constants/common.js";import{appendQueryParams as ie}from"../../utils/url/append-query-params.js";import{parsePreviewUrl as N}from"../../utils/url/parse-preview-url.js";import{logger as ce}from"../tools/notifiers/logger.js";import{randomString as ue}from"../utils/crypto/random-string.js";import{randomUUID as U}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as h,JwtTokenExpired as de}from"./jwt/types.js";import*as d from"./jwt/jwt.js";import{parseTeamClaimToArray as le}from"../utils/index.js";import{arrayBufferToBase64 as me,decodeBase64 as j,encodeBase64URL as pe,urlSafeBase64 as V}from"./jwt/encode.js";import{formatSamlCertificate as fe}from"./utils/format-saml-certificate.js";function $(e){return e?.type===u.OIDC}function he(e){return e?.type===u.SAML2}async function rt(e,t){if($(t))return ye(e,t);if(he(t))return we(e,t)}async function ye(e,t){const n=await q(e,t),r=new Set((t.scopes||[]).concat(v)),o=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function we(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}function B(e){return Array.from(new Set((e.scopes||[]).concat(v))).join(" ")}async function H(e,t,n){return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:t.clientId,...t.clientSecret?{client_secret:t.clientSecret}:{},...n}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(r=>r.json())}async function ot(e,t,n,r,o={}){return await H(e,r,{scope:B(r),code:t,redirect_uri:F(n),grant_type:"authorization_code",...o})}async function st(e,t,n){return await H(e,n,{scope:B(n),grant_type:"refresh_token",refresh_token:t})}function Se(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:s,idpId:a,pkce:l},p,x,f){if(!t||!n||!r||!o)return{loginUrl:void 0};const i=new URL(t),w=f?.redirectUriOverride??`${e}${se(b.OIDC_CALLBACK)}`,g={state:U(),idpId:a,redirectUri:w,redirectTo:p,branch:f?.branchOverride??_e(e),inviteCode:x,source:f?.sourceOverride??"portal",uiLocales:f?.uiLocales},S={};if(l){const m=V(ue(50)),k=V(Z("sha256").update(m).digest("base64")),I="S256";i.searchParams.append("code_challenge",k),i.searchParams.append("code_challenge_method",I),S.code_verifier={value:m,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:oe()||"/"}}}i.searchParams.append("client_id",n),i.searchParams.append("scope",o),i.searchParams.append("response_type",r),i.searchParams.append("redirect_uri",F(w)),i.searchParams.append("state",pe(JSON.stringify(g))),f?.uiLocales&&i.searchParams.append("ui_locales",f.uiLocales);for(const m in s)s[m]!==void 0&&i.searchParams.append(m,s[m]);return{loginUrl:i.toString(),cookies:S}}function at(e,t,n,r){const o=new URL(e);return o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n),o.toString()}async function it(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return d.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpId?{idp_id:e.idpId}:{},...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:n},y,h.HS256)}async function ct(e){await d.verify(e,y,h.HS256);const{payload:t}=d.decode(e);if(t.type!=="mcp_auth_code")throw new Error("Invalid authorization code type");if(!t.client_id||!t.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof t.exp=="number"&&Date.now()>=t.exp*1e3)throw new Error("Authorization code expired");return t}async function ut(e){const t=Math.floor(Date.now()/1e3);return d.sign({type:"mcp_refresh_token",client_id:e.clientId,...e.idpId?{idp_id:e.idpId}:{},claims:e.claims,...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},session_exp:e.sessionExp,iat:t,exp:e.sessionExp},y,h.HS256)}async function dt(e){await d.verify(e,y,h.HS256);const{payload:t}=d.decode(e);if(t.type!=="mcp_refresh_token")throw new Error("Invalid refresh token type");if(!t.client_id||typeof t.claims!="object"||t.claims===null)throw new Error("Refresh token missing required claims");if(typeof t.session_exp!="number")throw new Error("Refresh token missing session expiry");return t}function lt(e){const t=e||ee(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcpSession",uri:`urn:redocly:realm:mcp:session:${n}`}}function F(e){const t=N(e);if(!t)return e;const n=Ae(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,z(e,t);const r=W(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,z(e,t)):e}function _e(e){const t=N(e);if(t)return W(t.hostname)?.previewBranch}function z(e,t){return e.replace(/^https?:\/\/[^/?#]+/i,`${t.protocol}//${t.host}`)}function W(e){const t=e.split(".preview.",2);if(t.length<2)return null;const[n,r]=t,o=n.indexOf("--");if(o===-1)return null;const s=n.slice(0,o),a=n.slice(o+2);return!s||!a||a.includes(".")?null:{projectSlug:s,previewBranch:a,after:r}}function Ae(e){const t=e.indexOf("."),n=t===-1?e:e.slice(0,t);if(!n.startsWith("preview-"))return null;const r=n.indexOf("--");if(r===-1)return null;const o=n.slice(r+2);if(!o)return null;const s=t===-1?"":e.slice(t);return{previewBranch:o,after:s}}function xe(e){return e.type===u.OIDC}function ge(e){return e.type===u.SAML2}function mt(e,t,n,r,o){return xe(e)?Se(t,e,n,r,{uiLocales:o}):ge(e)?ke(t,e,n,r,o):{}}function ke(e,t,n,r,o){const a=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
2
2
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
3
3
  Version="2.0"
4
4
  ID="_${U()}"
5
5
  IssueInstant="${new Date().toISOString()}"
6
- AssertionConsumerServiceURL="${e}${P.SAML_CALLBACK}"
6
+ AssertionConsumerServiceURL="${e}${b.SAML_CALLBACK}"
7
7
  AttributeConsumingServiceIndex="0">
8
8
  <saml:Issuer>${t.entityId}</saml:Issuer>
9
9
  <samlp:NameIDPolicy
10
10
  AllowCreate="true"
11
11
  Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
12
- </samlp:AuthnRequest>`,d=Ie(a);return{loginUrl:ie(t.ssoUrl,{SAMLRequest:d,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function Ie(e){return me(Q(new TextEncoder().encode(e)).buffer)}function mt(e){const t=j(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=X(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function pt(e){try{return JSON.parse(j(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function ft(e){const t=new D().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,s=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",a=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],d=a&&a.nodeValue||void 0,p=c(t,"//*[local-name(.)='Audience']/text()")[0],x=p&&p.nodeValue||void 0,i=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",w=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],g=w&&w.nodeValue||"",S=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],m=S&&S.nodeValue||"",k=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],I=Te(k),C={},L=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(L.length)for(const R of L){const M=c(R,"./@Name")[0];if(M.nodeValue){const O=c(R,"./*[local-name(.)='AttributeValue']/text()")[0];O?.nodeValue&&(C[M.nodeValue]=O.nodeValue)}}return{uid:g,success:r,expiresAt:I,issuerId:d,entityId:x,attrs:C,cert:i,nameFormat:m,destination:s}}function Te(e){const t=typeof e?.nodeValue=="string"&&T(Date.parse(e.nodeValue)),n=T(Date.now()),r=T(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function T(e){return Math.floor(e/1e3)}const E={},A={jwks:{}};async function q(e,t){if(!E[e]){const n=t.configurationUrl?await J(t.configurationUrl):t.configuration;E[e]=Ee()?Ce(n):n}return E[e]}function Ee(){const e=ne.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}function Ce(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const n of Object.keys(t)){const r=t[n];typeof r=="string"&&r.includes("://localhost")&&(t[n]=r.replace("://localhost","://host.docker.internal"))}return t}async function Le(e){for(const t of Object.keys(e)){const n=e[t];if(!$(n))continue;const r=await q(t,n);if(r.jwks_uri){const o=await J(r.jwks_uri);for(const s of o.keys)A.jwks[s.kid]={...s,idpId:t}}}}async function J(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function ht(e){return fetch(`${te}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function yt(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Re(t.hostname,r))}function Re(e,t){return e===t||e.endsWith(`.${t}`)}async function wt(e,t){const n=new D().parseFromString(e,"application/xml"),r=c(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=fe(t),s=new Y({publicCert:o});s.loadSignature(r);try{return s.checkSignature(e)}catch{return!1}}function St(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(o=e);const s=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],a=s?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(a?s:void 0),o=o?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||s,email:o,email_verified:!0,teams:r?de(n[r]):[]}}function K(e,t={}){return e.map(n=>t[n]||n)}async function At(e,t){if(!t)return{};const n=t[_.AUTHORIZATION];if(!n)return{};try{const r=l.decode(n);if(r.header.alg===h.RS256){A.jwks[r.header.kid]===void 0&&await Le(e);const p=A.jwks[r.header.kid];if(!p)return A.jwks[r.header.kid]=null,{};await l.verify(n,p,h.RS256)}else await l.verify(n,y,h.HS256);const o=r.payload.idpId||A.jwks[r.header.kid]?.idpId,s=e[o]||{},a=De(s),d=Oe(s);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...K(r.payload.teams||[],d),..."defaultTeams"in s&&s.defaultTeams||[],...K("teamsClaimName"in s&&r.payload[a||""]||[],d),ae])),name:Me(r.payload),isAuthenticated:!0,idpAccessToken:re(r.payload.idp_access_token,y)||t[_.IDP_ACCESS_TOKEN],federatedAccessToken:t[_.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[_.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof le||ce.error("Malformed JWT token: %s",r.message)}return{}}function Me(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Oe(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function De(e){switch(e.type){case u.SAML2:return e.teamsAttributeName??b;case u.OIDC:return e.teamsClaimName;default:return b}}function c(e,t){return G.select(t,e)||[]}export{dt as buildLoginUrl,Se as buildOidcLoginUrl,st as buildOidcLogoutUrl,ke as buildSAML2LoginUrl,at as createMcpAuthorizationCode,ct as createMcpRefreshToken,lt as createMcpSessionResource,mt as decodeSamlResponse,Le as discoverJwks,Ie as encodeSAML2,St as extractUserClaims,nt as getAuthProviderLoginParams,ye as getOidcLoginParams,q as getOidcMetadata,ht as getRedoclyTokenPayload,we as getSaml2LoginParams,At as getUserParamsFromCookies,Me as getUsernameFromPayload,$ as isOidcProviderConfig,yt as isRedoclySso,he as isSaml2ProviderConfig,rt as oidcExchangeCodeForToken,A as oidcJwksCache,E as oidcMetadataCache,ot as oidcRefreshTokenGrant,pt as parseOidcState,Ae as parsePreviewBranch,ft as parseSamlResponse,F as rewritePreviewAuthRedirectUri,it as verifyMcpAuthorizationCode,ut as verifyMcpRefreshToken,wt as verifySAMLResponse};
12
+ </samlp:AuthnRequest>`,l=Ie(a);return{loginUrl:ie(t.ssoUrl,{SAMLRequest:l,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function Ie(e){return me(Q(new TextEncoder().encode(e)).buffer)}function pt(e){const t=j(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=X(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function ft(e){try{return JSON.parse(j(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function ht(e){const t=new M().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,s=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",a=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],l=a&&a.nodeValue||void 0,p=c(t,"//*[local-name(.)='Audience']/text()")[0],x=p&&p.nodeValue||void 0,i=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",w=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],g=w&&w.nodeValue||"",S=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],m=S&&S.nodeValue||"",k=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],I=Te(k),C={},R=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(R.length)for(const L of R){const O=c(L,"./@Name")[0];if(O.nodeValue){const D=c(L,"./*[local-name(.)='AttributeValue']/text()")[0];D?.nodeValue&&(C[O.nodeValue]=D.nodeValue)}}return{uid:g,success:r,expiresAt:I,issuerId:l,entityId:x,attrs:C,cert:i,nameFormat:m,destination:s}}function Te(e){const t=typeof e?.nodeValue=="string"&&T(Date.parse(e.nodeValue)),n=T(Date.now()),r=T(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function T(e){return Math.floor(e/1e3)}const E={},_={jwks:{}};async function q(e,t){if(!E[e]){const n=t.configurationUrl?await J(t.configurationUrl):t.configuration;E[e]=Ee()?Re(n):n}return E[e]}function Ee(){const e=ne.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}const Ce=["authorization_endpoint","end_session_endpoint"];function Re(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const n of Object.keys(t)){if(Ce.includes(String(n)))continue;const r=t[n];typeof r=="string"&&r.includes("://localhost")&&(t[n]=r.replace("://localhost","://host.docker.internal"))}return t}async function Le(e){for(const t of Object.keys(e)){const n=e[t];if(!$(n))continue;const r=await q(t,n);if(r.jwks_uri){const o=await J(r.jwks_uri);for(const s of o.keys)_.jwks[s.kid]={...s,idpId:t}}}}async function J(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function yt(e){return fetch(`${te}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function wt(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Oe(t.hostname,r))}function Oe(e,t){return e===t||e.endsWith(`.${t}`)}async function St(e,t){const n=new M().parseFromString(e,"application/xml"),r=c(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=fe(t),s=new Y({publicCert:o});s.loadSignature(r);try{return s.checkSignature(e)}catch{return!1}}function _t(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(o=e);const s=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],a=s?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(a?s:void 0),o=o?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||s,email:o,email_verified:!0,teams:r?le(n[r]):[]}}function K(e,t={}){return e.map(n=>t[n]||n)}async function At(e,t){if(!t)return{};const n=t[A.AUTHORIZATION];if(!n)return{};try{const r=d.decode(n);if(r.header.alg===h.RS256){_.jwks[r.header.kid]===void 0&&await Le(e);const p=_.jwks[r.header.kid];if(!p)return _.jwks[r.header.kid]=null,{};await d.verify(n,p,h.RS256)}else await d.verify(n,y,h.HS256);const o=r.payload.idpId||_.jwks[r.header.kid]?.idpId,s=e[o]||{},a=Pe(s),l=Me(s);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...K(r.payload.teams||[],l),..."defaultTeams"in s&&s.defaultTeams||[],...K("teamsClaimName"in s&&r.payload[a||""]||[],l),ae])),name:De(r.payload),isAuthenticated:!0,idpAccessToken:re(r.payload.idp_access_token,y)||t[A.IDP_ACCESS_TOKEN],federatedAccessToken:t[A.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[A.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof de||ce.error("Malformed JWT token: %s",r.message)}return{}}function De(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Me(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function Pe(e){switch(e.type){case u.SAML2:return e.teamsAttributeName??P;case u.OIDC:return e.teamsClaimName;default:return P}}function c(e,t){return G.select(t,e)||[]}export{mt as buildLoginUrl,Se as buildOidcLoginUrl,at as buildOidcLogoutUrl,ke as buildSAML2LoginUrl,it as createMcpAuthorizationCode,ut as createMcpRefreshToken,lt as createMcpSessionResource,pt as decodeSamlResponse,Le as discoverJwks,Ie as encodeSAML2,_t as extractUserClaims,rt as getAuthProviderLoginParams,ye as getOidcLoginParams,q as getOidcMetadata,yt as getRedoclyTokenPayload,we as getSaml2LoginParams,At as getUserParamsFromCookies,De as getUsernameFromPayload,$ as isOidcProviderConfig,wt as isRedoclySso,he as isSaml2ProviderConfig,ot as oidcExchangeCodeForToken,_ as oidcJwksCache,E as oidcMetadataCache,st as oidcRefreshTokenGrant,ft as parseOidcState,_e as parsePreviewBranch,ht as parseSamlResponse,F as rewritePreviewAuthRedirectUri,ct as verifyMcpAuthorizationCode,dt as verifyMcpRefreshToken,St as verifySAMLResponse};
@@ -1 +1 @@
1
- import{withPathPrefix as u}from"@redocly/theme/core/utils";import{OTEL_EXCLUDED_ROUTES as o}from"../../constants/common.js";import{telemetryTraceStep as a}from"../../telemetry/helpers/trace-step.js";import{getClientIp as m}from"../utils/get-client-ip.js";function A(){return async(e,r)=>{if(o.some(t=>e.req.path===u(t)))return r();await a("request",async t=>{t?.setAttribute("method",e.req.method||""),t?.setAttribute("host",e.req.header("host")||""),t?.setAttribute("url",e.req.url||""),t?.setAttribute("path",e.req.path||""),t?.setAttribute("user_agent",e.req.header("user-agent")||"");const s=e.get("auth")?.claims?.id||e.get("auth")?.claims?.sub,i=m(e.req.raw)||e.req.raw.context.remoteAddr?.ipAddress;t?.setAttribute("user_id",s),t?.setAttribute("user_ip",i),await r()})}}export{A as telemetryMiddleware};
1
+ import{withPathPrefix as u}from"@redocly/theme/core/utils";import{OTEL_EXCLUDED_ROUTES as o}from"../../constants/common.js";import{telemetryTraceStep as m}from"../../telemetry/helpers/trace-step.js";import{getClientIp as d}from"../utils/get-client-ip.js";function A(){return async(e,t)=>{if(o.some(r=>e.req.path===u(r)))return t();await m("com.redocly.request.performed",async r=>{r?.setAttribute("id",e.req.path||""),r?.setAttribute("object","request"),r?.setAttribute("uri",e.req.url||""),r?.setAttribute("method",e.req.method||""),r?.setAttribute("host",e.req.header("host")||""),r?.setAttribute("url",e.req.url||""),r?.setAttribute("path",e.req.path||""),r?.setAttribute("user_agent",e.req.header("user-agent")||"");const i=e.get("auth")?.claims?.id||e.get("auth")?.claims?.sub,s=d(e.req.raw)||e.req.raw.context.remoteAddr?.ipAddress;r?.setAttribute("user_id",i),r?.setAttribute("user_ip",s),await t()})}}export{A as telemetryMiddleware};
@@ -1 +1 @@
1
- import{telemetryTraceStep as o}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as d}from"../../../../constants/api.js";import{ALLOWED_CATALOG_QUERY_PARAMS as y}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as f}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as g}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as E}from"../../../providers/database/pagination/schemas.js";import{ENTITY_RELATION_TO_DATABASE as _}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";import{getRbacRestrictionsDataForCatalog as A}from"../helpers/get-rbac-restrictions-data-for-catalog.js";const b=["id","key","title","type","summary","source","source_file","created_at","updated_at","relation_type","version","revision"],p=async({catalogEntitiesService:i,store:a,ctx:e})=>o("catalog_entities.bff.related_entities.get_related_entities",async t=>{try{const r=e.req.param(d);if(!r)return t?.error(new Error("Entity key is required")),e.json({message:"Entity key is required"},400);t?.setAttribute("pathParams",JSON.stringify({entityKey:r}));const s=e.req.query();t?.setAttribute("queryParams",JSON.stringify(f(s,y)));const m=E(b,_).parse(s),{currentRbacTeamsForRead:l,excludedTypes:c,excludedEntities:u}=A({store:a,ctx:e}),n=await i.getRelatedEntities({entityKey:r,paginationParams:m,rbacTeams:l,excludedTypes:c,excludedEntities:u});return t?.setAttribute("relatedEntitiesCount",n.items.length),e.json(n)}catch(r){return console.error(r),t?.error(r),e.json({message:r.message},500)}});function v(i){return async a=>o("catalog_entities.bff.related_entities",async e=>{e?.setAttribute("method",a.req.method);const t=await g.getInstance({baseDbDir:i.serverOutDir});return p({catalogEntitiesService:t,ctx:a,store:i})})}export{v as bffCatalogRelatedEntitiesHandler};
1
+ import{telemetryTraceStep as n}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as d}from"../../../../constants/api.js";import{ALLOWED_CATALOG_QUERY_PARAMS as y}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as E}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as g}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as b}from"../../../providers/database/pagination/schemas.js";import{ENTITY_RELATION_TO_DATABASE as A}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";import{getRbacRestrictionsDataForCatalog as f}from"../helpers/get-rbac-restrictions-data-for-catalog.js";const T=["id","key","title","type","summary","source","source_file","created_at","updated_at","relation_type","version","revision"],_=async({catalogEntitiesService:i,store:a,ctx:t})=>n("com.redocly.catalogEntities.bff.relatedEntities.getRelatedEntities",async e=>{e?.setAttribute("object","catalogEntity");try{const r=t.req.param(d);if(!r)return e?.error(new Error("Entity key is required")),t.json({message:"Entity key is required"},400);e?.setAttribute("id",r),e?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${r}`),e?.setAttribute("pathParams",JSON.stringify({entityKey:r}));const o=t.req.query();e?.setAttribute("queryParams",JSON.stringify(E(o,y)));const l=b(T,A).parse(o),{currentRbacTeamsForRead:m,excludedTypes:u,excludedEntities:c}=f({store:a,ctx:t}),s=await i.getRelatedEntities({entityKey:r,paginationParams:l,rbacTeams:m,excludedTypes:u,excludedEntities:c});return e?.setAttribute("relatedEntitiesCount",s.items.length),t.json(s)}catch(r){return console.error(r),e?.error(r),t.json({message:r.message},500)}});function I(i){return async a=>n("com.redocly.catalogEntities.bff.relatedEntities",async t=>{t?.setAttribute("id","catalogEntityId"),t?.setAttribute("object","catalogEntity"),t?.setAttribute("uri","urn:redocly:realm:api:catalogEntity:catalogEntityId"),t?.setAttribute("method",a.req.method);const e=await g.getInstance({baseDbDir:i.serverOutDir});return _({catalogEntitiesService:e,ctx:a,store:i})})}export{I as bffCatalogRelatedEntitiesHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as y}from"../../../telemetry/helpers/trace-step.js";import{parseFilterQuery as u}from"../../../providers/database/pagination/filter.js";import{extractEqualFilterClauseValue as f}from"../../../providers/database/pagination/utils/extract-equal-filter-clause-value.js";import{CatalogEntitiesService as c}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";function p(o){return async e=>y("catalog_entities.bff.revisions.get_entity_revisions",async t=>{const l=await c.getInstance({baseDbDir:o.serverOutDir}),r=e.req.query("filter");let i;try{i=u(typeof r=="string"?r:"",["catalogEntityKey"])}catch{return t?.error(new Error("Invalid filter")),e.json({message:"Invalid filter"},400)}const n=f(i,"catalogEntityKey");if(!n)return t?.error(new Error("Missing catalogEntityKey in filter")),e.json({message:"The filter query must include a non-empty catalogEntityKey clause, for example filter=catalogEntityKey:my-entity."},400);const s=e.req.query("version")??null;t?.setAttribute("queryParams",JSON.stringify({version:s,filter:r}));const a=await l.listEntityRevisions(n,s);return t?.setAttribute("totalRevisions",a.length),e.json({items:a})})}export{p as bffCatalogRevisionsHandler};
1
+ import{telemetryTraceStep as y}from"../../../telemetry/helpers/trace-step.js";import{parseFilterQuery as u}from"../../../providers/database/pagination/filter.js";import{extractEqualFilterClauseValue as c}from"../../../providers/database/pagination/utils/extract-equal-filter-clause-value.js";import{CatalogEntitiesService as f}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";function b(a){return async e=>y("com.redocly.catalogEntities.bff.revisions.getEntityRevisions",async t=>{t?.setAttribute("object","catalogEntity");const l=await f.getInstance({baseDbDir:a.serverOutDir}),i=e.req.query("filter");let n;try{n=u(typeof i=="string"?i:"",["catalogEntityKey"])}catch{return t?.error(new Error("Invalid filter")),e.json({message:"Invalid filter"},400)}const r=c(n,"catalogEntityKey");if(!r)return t?.error(new Error("Missing catalogEntityKey in filter")),e.json({message:"The filter query must include a non-empty catalogEntityKey clause, for example filter=catalogEntityKey:my-entity."},400);t?.setAttribute("id",r),t?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${r}`);const s=e.req.query("version")??null;t?.setAttribute("queryParams",JSON.stringify({version:s,filter:i}));const o=await l.listEntityRevisions(r,s);return t?.setAttribute("totalRevisions",o.length),e.json({items:o})})}export{b as bffCatalogRevisionsHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as d}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as g}from"../../../../constants/api.js";import{ALLOWED_CATALOG_QUERY_PARAMS as p}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as E}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as b}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as v}from"../../../providers/database/pagination/schemas.js";import{isValidIsoDate as q}from"../../../utils/is-valid-iso-date.js";import{isValidSanitizedString as A}from"../../../utils/validate-and-sanitize-string.js";import{getRbacRestrictionsDataForCatalog as l}from"../helpers/get-rbac-restrictions-data-for-catalog.js";const _=["type","key","title","summary","tags","metadata","metadata.*","git","contact","links","id","source","source_file","version","revision","created_at","updated_at","domains","owners"],h=async({catalogEntitiesService:r,ctx:e,store:n})=>d("catalog_entities.bff.get_entities",async t=>{const i=e.req.query();t?.setAttribute("queryParams",JSON.stringify(E(i,p)));const a=v(_).parse(i),{currentRbacTeamsForRead:s,excludedTypes:o,excludedEntities:u}=l({store:n,ctx:e}),m=await r.getEntitiesWithRelations({paginationParams:a,rbacTeams:s,excludedTypes:o,excludedEntities:u});return t?.setAttribute("entitiesCount",m.items.length),e.json(m)}),w=async({catalogEntitiesService:r,ctx:e,store:n})=>d("catalog_entities.bff.get_entity",async t=>{const i=e.req.param(g);if(!i)return t?.error(new Error("Entity key is required")),e.json({message:"Entity key is required"},400);const a=e.req.query("revision");let s=null;if(a){if(!q(a))return t?.error(new Error("Invalid revision parameter")),e.json({message:"Invalid revision parameter: must be a valid ISO 8601 date-time string"},400);s=a}const o=e.req.query("version");if(!A(o,{pattern:/^[a-zA-Z0-9._-]+$/,maxLength:100,allowEmpty:!0}))return t?.error(new Error("Invalid version parameter")),e.json({message:"Invalid version parameter: version must contain only alphanumeric characters, dots, hyphens, and underscores, and must not exceed 100 characters"},400);const u=e.req.query();t?.setAttribute("queryParams",JSON.stringify(u)),t?.setAttribute("pathParams",JSON.stringify({entityKey:i}));const{currentRbacTeamsForRead:m,excludedTypes:c,excludedEntities:f}=l({store:n,ctx:e}),y=await r.getEntityWithRelationsByKey({entityKey:i,filter:{revision:s,version:o},rbacTeams:m,excludedTypes:c,excludedEntities:f});return y?(t?.setAttribute("entity",JSON.stringify(y)),e.json(y)):(t?.error(new Error("Entity not found")),e.json({message:"Entity not found"},404))});function L(r){return async e=>d("catalog_entities.bff",async n=>{n?.setAttribute("method",e.req.method);const t=await b.getInstance({baseDbDir:r.serverOutDir});return e.req.param("entityKey")?w({catalogEntitiesService:t,ctx:e,store:r}):h({catalogEntitiesService:t,ctx:e,store:r})})}export{L as bffCatalogHandler};
1
+ import{telemetryTraceStep as l}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as E}from"../../../../constants/api.js";import{ALLOWED_CATALOG_QUERY_PARAMS as b}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as f}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as A}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as v}from"../../../providers/database/pagination/schemas.js";import{isValidIsoDate as p}from"../../../utils/is-valid-iso-date.js";import{isValidSanitizedString as q}from"../../../utils/validate-and-sanitize-string.js";import{getRbacRestrictionsDataForCatalog as d}from"../helpers/get-rbac-restrictions-data-for-catalog.js";const h=["type","key","title","summary","tags","metadata","metadata.*","git","contact","links","id","source","source_file","version","revision","created_at","updated_at","domains","owners"],w=async({catalogEntitiesService:n,ctx:t,store:i})=>l("com.redocly.catalogEntities.bff.getEntities",async e=>{e?.setAttribute("id","catalogEntityId"),e?.setAttribute("object","catalogEntity"),e?.setAttribute("uri","urn:redocly:realm:api:catalogEntity:catalogEntityId");const r=t.req.query();e?.setAttribute("queryParams",JSON.stringify(f(r,b)));const a=v(h).parse(r),{currentRbacTeamsForRead:s,excludedTypes:o,excludedEntities:y}=d({store:i,ctx:t}),u=await n.getEntitiesWithRelations({paginationParams:a,rbacTeams:s,excludedTypes:o,excludedEntities:y});return e?.setAttribute("entitiesCount",u.items.length),t.json(u)}),I=async({catalogEntitiesService:n,ctx:t,store:i})=>l("com.redocly.catalogEntities.bff.getEntity",async e=>{e?.setAttribute("object","catalogEntity");const r=t.req.param(E);if(!r)return e?.error(new Error("Entity key is required")),t.json({message:"Entity key is required"},400);e?.setAttribute("id",r),e?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${r}`);const a=t.req.query("revision");let s=null;if(a){if(!p(a))return e?.error(new Error("Invalid revision parameter")),t.json({message:"Invalid revision parameter: must be a valid ISO 8601 date-time string"},400);s=a}const o=t.req.query("version");if(!q(o,{pattern:/^[a-zA-Z0-9._-]+$/,maxLength:100,allowEmpty:!0}))return e?.error(new Error("Invalid version parameter")),t.json({message:"Invalid version parameter: version must contain only alphanumeric characters, dots, hyphens, and underscores, and must not exceed 100 characters"},400);const y=t.req.query();e?.setAttribute("queryParams",JSON.stringify(y)),e?.setAttribute("pathParams",JSON.stringify({entityKey:r}));const{currentRbacTeamsForRead:u,excludedTypes:c,excludedEntities:g}=d({store:i,ctx:t}),m=await n.getEntityWithRelationsByKey({entityKey:r,filter:{revision:s,version:o},rbacTeams:u,excludedTypes:c,excludedEntities:g});return m?(e?.setAttribute("entity",JSON.stringify(m)),t.json(m)):(e?.error(new Error("Entity not found")),t.json({message:"Entity not found"},404))});function L(n){return async t=>l("com.redocly.catalogEntities.bff",async i=>{i?.setAttribute("id","catalogEntityId"),i?.setAttribute("object","catalogEntity"),i?.setAttribute("uri","urn:redocly:realm:api:catalogEntity:catalogEntityId"),i?.setAttribute("method",t.req.method);const e=await A.getInstance({baseDbDir:n.serverOutDir});return t.req.param("entityKey")?I({catalogEntitiesService:e,ctx:t,store:n}):w({catalogEntitiesService:e,ctx:t,store:n})})}export{L as bffCatalogHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as l}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_RELATION_ID as y}from"../../../../constants/api.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as m,ALLOWED_CATALOG_QUERY_PARAMS as c}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as E}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as f}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as R}from"../../../providers/database/pagination/schemas.js";import{ENTITY_RELATION_TO_DATABASE as w}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";import{CacheService as A}from"../../../persistence/cache/services/cache-service.js";import{parseEntityRelation as _}from"./parsers/relations/parse-entity-relation.js";import{parseEntitiesRelations as b}from"./parsers/relations/parse-entities-relations.js";import{parseEntityRelationUpdateData as j}from"./parsers/relations/parse-entity-relation-update-data.js";const h=["id","organizationId","projectId","sourceKey","targetKey","type","createdAt","updatedAt"],g=async a=>{await(await A.getInstance({baseDbDir:a,databaseType:"local"})).deleteByNamespace(m)},p=async({catalogEntitiesService:a,ctx:e})=>l("catalog_entities.relations.get_entity_relation",async n=>{const t=e.req.param(y);if(!t)return n?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);n?.setAttribute("pathParams",JSON.stringify({entityRelationId:t}));const i=await a.getEntityRelationById(t);return i?(n?.setAttribute("entityRelation",JSON.stringify(i)),e.json(i)):(n?.error(new Error("Entity relation not found")),e.json({message:"Entity relation not found"},404))}),O=async({catalogEntitiesService:a,ctx:e})=>l("catalog_entities.relations.get_entities_relations",async n=>{try{const t=e.req.query();n?.setAttribute("queryParams",JSON.stringify(E(t,c)));const i=R(h,w).parse(t),r=await a.getEntitiesRelations(i);return n?.setAttribute("entitiesRelationsCount",r.items.length),e.json(r)}catch(t){return console.error(t),n?.error(new Error("Failed to get entities relations")),e.json({message:"Failed to get entities relations"},500)}}),q=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.create_entity_relation",async t=>{const i=e.get("logger");try{const r=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(r));const o=_(r),s=await a.createEntityRelation(o);return s?(g(n),t?.setAttribute("entityRelation",JSON.stringify(s)),e.json(s)):(t?.error(new Error("Failed to create entity relation")),e.json({message:"Failed to create entity relation"},500))}catch(r){return i.error(r),r instanceof Error&&r.message.includes("validation failed")?(t?.error(new Error(r.message)),e.json({message:r.message},400)):(t?.error(r),e.json({message:"Failed to create entity relation"},500))}}),T=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.bulk_upsert_entities_relations",async t=>{const i=e.get("logger");try{const r=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(r));const o=b(r),s=await a.createEntitiesRelations(o);if(!s.length)return t?.error(new Error("Failed to create entity relations")),e.json({message:"Failed to create entity relations"},500);g(n);const u=s.filter(d=>d.status==="ok");return t?.setAttribute("totalSuccess",u.length),t?.setAttribute("totalFailed",s.length-u.length),e.json(s,207)}catch(r){return i.error(r),r instanceof Error&&r.message.includes("validation failed")?(t?.error(new Error(r.message)),e.json({message:r.message},400)):(t?.error(r),e.json({message:"Failed to create entity relations"},500))}}),N=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.update_entity_relation",async t=>{const i=e.get("logger"),r=e.req.param(y);if(!r)return t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);t?.setAttribute("pathParams",JSON.stringify({entityRelationId:r}));try{const o=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(o));const s=await a.getEntityRelationById(r);if(!s)return t?.error(new Error(`Entity relation with id: ${r} not found`)),e.json({message:`Entity relation with id: ${r} not found`},404);const u=j(o),d=await a.updateEntityRelation(u,s);return d?(g(n),t?.setAttribute("entityRelation",JSON.stringify(d)),e.json(d)):(t?.error(new Error("Failed to update entity relation")),e.json({message:"Failed to update entity relation"},500))}catch(o){return i.error(o),o instanceof Error&&o.message.includes("validation failed")?(t?.error(new Error(o.message)),e.json({message:o.message},400)):(t?.error(o),e.json({message:o.message},500))}}),I=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.delete_entity_relation",async t=>{const i=e.req.param(y);return i?(t?.setAttribute("pathParams",JSON.stringify({entityRelationId:i})),await a.deleteEntityRelation(i),g(n),new Response(null,{status:204})):(t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400))}),D={GET:O,POST:q,PUT:T},F={GET:p,DELETE:I,PATCH:N};function Y(a){return async e=>l("catalog_entities.relations",async n=>{n?.setAttribute("method",e.req.method);const t=await f.getInstance({baseDbDir:a.serverOutDir}),i=e.req.method,r=e.req.param(y)?F:D;return r[i]?await r[i]({catalogEntitiesService:t,ctx:e,serverOutDir:a.serverOutDir}):(n?.error(new Error("Method not allowed")),e.json({message:"Method not allowed"},405))})}export{Y as catalogRelationsHandler};
1
+ import{telemetryTraceStep as l}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_RELATION_ID as g}from"../../../../constants/api.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as E,ALLOWED_CATALOG_QUERY_PARAMS as c}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as m}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as R}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as A}from"../../../providers/database/pagination/schemas.js";import{ENTITY_RELATION_TO_DATABASE as b}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";import{CacheService as f}from"../../../persistence/cache/services/cache-service.js";import{parseEntityRelation as w}from"./parsers/relations/parse-entity-relation.js";import{parseEntitiesRelations as j}from"./parsers/relations/parse-entities-relations.js";import{parseEntityRelationUpdateData as h}from"./parsers/relations/parse-entity-relation-update-data.js";const O=["id","organizationId","projectId","sourceKey","targetKey","type","createdAt","updatedAt"],y=async o=>{await(await f.getInstance({baseDbDir:o,databaseType:"local"})).deleteByNamespace(E)},q=async({catalogEntitiesService:o,ctx:e})=>l("com.redocly.catalogEntities.relations.getEntityRelation",async r=>{r?.setAttribute("object","catalogEntitiesRelation");const t=e.req.param(g);if(!t)return r?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);r?.setAttribute("id",t),r?.setAttribute("uri",`urn:redocly:realm:api:catalogEntitiesRelation:${t}`),r?.setAttribute("pathParams",JSON.stringify({entityRelationId:t}));const n=await o.getEntityRelationById(t);return n?(r?.setAttribute("entityRelation",JSON.stringify(n)),e.json(n)):(r?.error(new Error("Entity relation not found")),e.json({message:"Entity relation not found"},404))}),I=async({catalogEntitiesService:o,ctx:e})=>l("com.redocly.catalogEntities.relations.getEntitiesRelations",async r=>{r?.setAttribute("id","catalogEntitiesRelationId"),r?.setAttribute("object","catalogEntitiesRelation"),r?.setAttribute("uri","urn:redocly:realm:api:catalogEntitiesRelation:catalogEntitiesRelationId");try{const t=e.req.query();r?.setAttribute("queryParams",JSON.stringify(m(t,c)));const n=A(O,b).parse(t),i=await o.getEntitiesRelations(n);return r?.setAttribute("entitiesRelationsCount",i.items.length),e.json(i)}catch(t){return console.error(t),r?.error(new Error("Failed to get entities relations")),e.json({message:"Failed to get entities relations"},500)}}),T=async({catalogEntitiesService:o,ctx:e,serverOutDir:r})=>l("com.redocly.catalogEntities.relations.createEntityRelation",async t=>{t?.setAttribute("object","catalogEntitiesRelation");const n=e.get("logger");try{const i=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(i));const a=w(i),s=await o.createEntityRelation(a);return s?(y(r),t?.setAttribute("id",s.id),t?.setAttribute("uri",`urn:redocly:realm:api:catalogEntitiesRelation:${s.id}`),t?.setAttribute("entityRelation",JSON.stringify(s)),e.json(s)):(t?.error(new Error("Failed to create entity relation")),e.json({message:"Failed to create entity relation"},500))}catch(i){return n.error(i),i instanceof Error&&i.message.includes("validation failed")?(t?.error(new Error(i.message)),e.json({message:i.message},400)):(t?.error(i),e.json({message:"Failed to create entity relation"},500))}}),N=async({catalogEntitiesService:o,ctx:e,serverOutDir:r})=>l("com.redocly.catalogEntities.relations.bulkUpsertEntitiesRelations",async t=>{t?.setAttribute("id","catalogEntitiesRelationId"),t?.setAttribute("object","catalogEntitiesRelation"),t?.setAttribute("uri","urn:redocly:realm:api:catalogEntitiesRelation:catalogEntitiesRelationId");const n=e.get("logger");try{const i=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(i));const a=j(i),s=await o.createEntitiesRelations(a);if(!s.length)return t?.error(new Error("Failed to create entity relations")),e.json({message:"Failed to create entity relations"},500);y(r);const d=s.filter(u=>u.status==="ok");return t?.setAttribute("totalSuccess",d.length),t?.setAttribute("totalFailed",s.length-d.length),e.json(s,207)}catch(i){return n.error(i),i instanceof Error&&i.message.includes("validation failed")?(t?.error(new Error(i.message)),e.json({message:i.message},400)):(t?.error(i),e.json({message:"Failed to create entity relations"},500))}}),D=async({catalogEntitiesService:o,ctx:e,serverOutDir:r})=>l("com.redocly.catalogEntities.relations.updateEntityRelation",async t=>{t?.setAttribute("object","catalogEntitiesRelation");const n=e.get("logger"),i=e.req.param(g);if(!i)return t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);t?.setAttribute("id",i),t?.setAttribute("uri",`urn:redocly:realm:api:catalogEntitiesRelation:${i}`),t?.setAttribute("pathParams",JSON.stringify({entityRelationId:i}));try{const a=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(a));const s=await o.getEntityRelationById(i);if(!s)return t?.error(new Error(`Entity relation with id: ${i} not found`)),e.json({message:`Entity relation with id: ${i} not found`},404);const d=h(a),u=await o.updateEntityRelation(d,s);return u?(y(r),t?.setAttribute("entityRelation",JSON.stringify(u)),e.json(u)):(t?.error(new Error("Failed to update entity relation")),e.json({message:"Failed to update entity relation"},500))}catch(a){return n.error(a),a instanceof Error&&a.message.includes("validation failed")?(t?.error(new Error(a.message)),e.json({message:a.message},400)):(t?.error(a),e.json({message:a.message},500))}}),F=async({catalogEntitiesService:o,ctx:e,serverOutDir:r})=>l("com.redocly.catalogEntities.relations.deleteEntityRelation",async t=>{t?.setAttribute("object","catalogEntitiesRelation");const n=e.req.param(g);return n?(t?.setAttribute("id",n),t?.setAttribute("uri",`urn:redocly:realm:api:catalogEntitiesRelation:${n}`),t?.setAttribute("pathParams",JSON.stringify({entityRelationId:n})),await o.deleteEntityRelation(n),y(r),new Response(null,{status:204})):(t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400))}),P={GET:I,POST:T,PUT:N},C={GET:q,DELETE:F,PATCH:D};function k(o){return async e=>l("com.redocly.catalogEntities.relations",async r=>{r?.setAttribute("id","catalogEntitiesRelationId"),r?.setAttribute("object","catalogEntitiesRelation"),r?.setAttribute("uri","urn:redocly:realm:api:catalogEntitiesRelation:catalogEntitiesRelationId"),r?.setAttribute("method",e.req.method);const t=await R.getInstance({baseDbDir:o.serverOutDir}),n=e.req.method,i=e.req.param(g)?C:P;return i[n]?await i[n]({catalogEntitiesService:t,ctx:e,serverOutDir:o.serverOutDir}):(r?.error(new Error("Method not allowed")),e.json({message:"Method not allowed"},405))})}export{k as catalogRelationsHandler};
@@ -1 +1 @@
1
- import{getCompleteCatalogConfig as j}from"../../../plugins/catalog-entities/get-complete-catalog-config.js";import{telemetryTraceStep as m}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as T,ALLOWED_CATALOG_QUERY_PARAMS as F}from"../../../constants/plugins/catalog-entities.js";import{CATALOG_ENTITY_ID as f}from"../../../../constants/api.js";import{allowlistObject as _}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as R}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as q}from"../../../providers/database/pagination/schemas.js";import{createEntityRelationDtoFromFileSchema as O}from"../../../plugins/catalog-entities/database/mappers/create-entity-relation-dto-from-file-schema.js";import{CacheService as C}from"../../../persistence/cache/services/cache-service.js";import{hasAccessToEntity as w}from"./helpers/has-access-to-entity.js";import{getRbacRestrictionsDataForCatalog as b}from"../helpers/get-rbac-restrictions-data-for-catalog.js";import{mapEntityReadModelSchemaToEntityReadDto as g}from"./mappers/map-entity-read-model-schema-to-entity-read-dto.js";import{parseEntity as I}from"./parsers/entities/parse-entity.js";import{parseEntityUpdateData as D}from"./parsers/entities/parse-entity-update-data.js";import{parseEntities as P}from"./parsers/entities/parse-entities.js";import{upsertPagesStats as h}from"./helpers/upsert-pages-stats.js";const k=["type","key","title","summary","tags","metadata","metadata.*","git","contact","links","id","source","sourceFile","createdAt","updatedAt"],A=async n=>{await(await C.getInstance({baseDbDir:n,databaseType:"local"})).deleteByNamespace(T)},N=async({catalogEntitiesService:n,ctx:e,store:u})=>m("catalog_entities.get_entities",async o=>{const i=e.get("logger");try{const r=e.req.query();o?.setAttribute("queryParams",JSON.stringify(_(r,F)));const{currentRbacTeamsForRead:d,excludedTypes:t,excludedEntities:s}=b({store:u,ctx:e}),l=q(k).parse(r),y=await n.getEntities({paginationParams:l,rbacTeams:d,excludedTypes:t,excludedEntities:s});return o?.setAttribute("entitiesCount",y.items.length),e.json({...y,items:y.items.map(g)})}catch(r){return i.error(r),o?.error(r),e.json({message:"Failed to get entities"},500)}}),W=async({catalogEntitiesService:n,ctx:e,store:u})=>m("catalog_entities.get_entity",async o=>{const i=e.req.param(f);if(!i)return o?.error(new Error("Entity id is required")),e.json({message:"Entity id is required"},400);o?.setAttribute("pathParams",JSON.stringify({entityId:i}));const{currentRbacTeamsForRead:r,excludedTypes:d,excludedEntities:t}=b({store:u,ctx:e}),s=await n.getEntityById(i,{rbacTeams:r,excludedTypes:d,excludedEntities:t});return s?(o?.setAttribute("entity",JSON.stringify(s)),e.json(g(s))):(o?.error(new Error("Entity not found")),e.json({message:"Entity not found"},404))}),L=async({catalogEntitiesService:n,ctx:e,catalogConfig:u,serverOutDir:o,store:i})=>m("catalog_entities.create_entity",async r=>{const d=e.get("logger");try{const t=await e.req.json();r?.setAttribute("requestBody",JSON.stringify(t));const s=I(t,u);if(!w({ctx:e,store:i,accessLevel:"WRITE",entityType:s.type,entityKey:s.key}))return r?.error(new Error("Access denied")),e.json({message:"Access denied"},403);const y=await n.transaction(async()=>{const a=await n.createEntity({entity:s,source:"remote",errorOnSkip:!0}),c=s.relations?.map(E=>O(s.key,E));return await n.createEntitiesRelations(c??[]),await h({ctx:e,catalogEntitiesService:n,addedEntities:a?[a]:void 0}),a});return y?(A(o),r?.setAttribute("entity",JSON.stringify(y)),e.json(g(y))):(r?.error(new Error("Failed to create entity")),e.json({message:"Failed to create entity"},500))}catch(t){return d.error(t),t instanceof Error&&t.message.includes("validation failed")?(r?.error(new Error(t.message)),e.json({message:t.message},400)):(r?.error(t),e.json({message:"Failed to create entity"},500))}}),J=async({catalogEntitiesService:n,ctx:e,catalogConfig:u,serverOutDir:o,store:i})=>m("catalog_entities.bulk_upsert_entities",async r=>{const d=e.get("logger");try{const t=await e.req.json();r?.setAttribute("requestBody",JSON.stringify(t));const s=P(t,u);for(const a of s)if(!w({ctx:e,store:i,accessLevel:"WRITE",entityType:a.type,entityKey:a.key}))return r?.error(new Error("Access denied")),e.json({message:"Access denied"},403);const l=await n.transaction(async()=>{const a=await n.createEntities(s,"remote"),c=a.reduce((E,p)=>(p.status==="ok"&&p.resource!=null&&E.push(p.resource),E),[]);return await h({ctx:e,catalogEntitiesService:n,addedEntities:c}),a});if(!l.length)return r?.error(new Error("Failed to create entities")),e.json({message:"Failed to create entities"},500);A(o);const y=l.filter(a=>a.status==="ok");return r?.setAttribute("totalSuccess",y.length),r?.setAttribute("totalFailed",l.length-y.length),e.json(y.map(a=>({...a,resource:g(a.resource)})),207)}catch(t){return d.error(t),t instanceof Error&&t.message.includes("validation failed")?(r?.error(new Error(t.message)),e.json({message:t.message},400)):(r?.error(t),e.json({message:"Failed to create entities"},500))}}),v=async({catalogEntitiesService:n,ctx:e,catalogConfig:u,serverOutDir:o,store:i})=>m("catalog_entities.update_entity",async r=>{const d=e.get("logger"),t=e.req.param(f);if(!t)return r?.error(new Error("Entity id is required")),e.json({message:"Entity id is required"},400);r?.setAttribute("pathParams",JSON.stringify({entityId:t}));try{const s=await e.req.json();r?.setAttribute("requestBody",JSON.stringify(s));const l=await n.getEntityById(t);if(!l)return r?.error(new Error(`Entity with id: ${t} not found`)),e.json({message:`Entity with id: ${t} not found`},404);if(l.source==="file")return r?.error(new Error("File entities cannot be updated")),e.json({message:"File entities cannot be updated"},400);if(!w({ctx:e,store:i,accessLevel:"WRITE",entityType:s.type??l.type,entityKey:s.key??l.key}))return r?.error(new Error("Access denied")),e.json({message:"Access denied"},403);const a=D(s,u,s?.type??l.type),c=await n.updateEntity(a,l);return c?(A(o),r?.setAttribute("entity",JSON.stringify(c)),e.json(g(c))):(r?.error(new Error("Failed to update entity")),e.json({message:"Failed to update entity"},500))}catch(s){return d.error(s),s instanceof Error&&s.message.includes("validation failed")?(r?.error(new Error(s.message)),e.json({message:s.message},400)):(r?.error(s),e.json({message:s.message},500))}}),B=async({catalogEntitiesService:n,ctx:e,serverOutDir:u,store:o})=>m("catalog_entities.delete_entity",async i=>{const r=e.get("logger"),d=e.req.param(f);if(!d)return i?.error(new Error("Entity id is required")),e.json({message:"Entity id is required"},400);i?.setAttribute("pathParams",JSON.stringify({entityId:d}));try{const t=await n.getEntityById(d);return t?t.source==="file"?(i?.error(new Error("File entities cannot be deleted")),e.json({message:"File entities cannot be deleted"},400)):w({ctx:e,store:o,accessLevel:"WRITE",entityType:t.type,entityKey:t.key})?await n.transaction(async()=>{const y=await n.deleteEntity(t);return y?(await h({ctx:e,catalogEntitiesService:n,removedEntities:t?[t]:void 0}),y):null})?(A(u),new Response(null,{status:204})):(i?.error(new Error("Failed to delete entity")),e.json({message:"Failed to delete entity"},500)):(i?.error(new Error("Access denied")),e.json({message:"Access denied"},403)):new Response(null,{status:204})}catch(t){return r.error(t),i?.error(t),e.json({message:"Failed to delete entity"},500)}}),U={GET:N,POST:L,PUT:J},G={GET:W,DELETE:B,PATCH:v};function ne(n){return async e=>m("catalog_entities",async u=>{const o=await R.getInstance({baseDbDir:n.serverOutDir}),i=e.req.method;u?.setAttribute("method",i);const r=e.req.param(f)?G:U,d=j(n.config.entitiesCatalog),t=r[i];return t?await t({catalogEntitiesService:o,ctx:e,catalogConfig:d,serverOutDir:n.serverOutDir,store:n}):(u?.error(new Error("Method not allowed")),e.json({message:"Method not allowed"},405))})}export{ne as catalogHandler};
1
+ import{getCompleteCatalogConfig as T}from"../../../plugins/catalog-entities/get-complete-catalog-config.js";import{telemetryTraceStep as g}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as p,ALLOWED_CATALOG_QUERY_PARAMS as F}from"../../../constants/plugins/catalog-entities.js";import{CATALOG_ENTITY_ID as f}from"../../../../constants/api.js";import{allowlistObject as I}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as R}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as q}from"../../../providers/database/pagination/schemas.js";import{createEntityRelationDtoFromFileSchema as O}from"../../../plugins/catalog-entities/database/mappers/create-entity-relation-dto-from-file-schema.js";import{CacheService as C}from"../../../persistence/cache/services/cache-service.js";import{hasAccessToEntity as A}from"./helpers/has-access-to-entity.js";import{getRbacRestrictionsDataForCatalog as h}from"../helpers/get-rbac-restrictions-data-for-catalog.js";import{mapEntityReadModelSchemaToEntityReadDto as m}from"./mappers/map-entity-read-model-schema-to-entity-read-dto.js";import{parseEntity as D}from"./parsers/entities/parse-entity.js";import{parseEntityUpdateData as P}from"./parsers/entities/parse-entity-update-data.js";import{parseEntities as k}from"./parsers/entities/parse-entities.js";import{upsertPagesStats as j}from"./helpers/upsert-pages-stats.js";const N=["type","key","title","summary","tags","metadata","metadata.*","git","contact","links","id","source","sourceFile","createdAt","updatedAt"],b=async s=>{await(await C.getInstance({baseDbDir:s,databaseType:"local"})).deleteByNamespace(p)},W=async({catalogEntitiesService:s,ctx:t,store:a})=>g("com.redocly.catalogEntities.getEntities",async o=>{o?.setAttribute("id","catalogEntityId"),o?.setAttribute("object","catalogEntity"),o?.setAttribute("uri","urn:redocly:realm:api:catalogEntity:catalogEntityId");const n=t.get("logger");try{const e=t.req.query();o?.setAttribute("queryParams",JSON.stringify(I(e,F)));const{currentRbacTeamsForRead:d,excludedTypes:r,excludedEntities:i}=h({store:a,ctx:t}),l=q(N).parse(e),y=await s.getEntities({paginationParams:l,rbacTeams:d,excludedTypes:r,excludedEntities:i});return o?.setAttribute("entitiesCount",y.items.length),t.json({...y,items:y.items.map(m)})}catch(e){return n.error(e),o?.error(e),t.json({message:"Failed to get entities"},500)}}),L=async({catalogEntitiesService:s,ctx:t,store:a})=>g("com.redocly.catalogEntities.getEntity",async o=>{o?.setAttribute("object","catalogEntity");const n=t.req.param(f);if(!n)return o?.error(new Error("Entity id is required")),t.json({message:"Entity id is required"},400);o?.setAttribute("id",n),o?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${n}`),o?.setAttribute("pathParams",JSON.stringify({entityId:n}));const{currentRbacTeamsForRead:e,excludedTypes:d,excludedEntities:r}=h({store:a,ctx:t}),i=await s.getEntityById(n,{rbacTeams:e,excludedTypes:d,excludedEntities:r});return i?(o?.setAttribute("entity",JSON.stringify(i)),t.json(m(i))):(o?.error(new Error("Entity not found")),t.json({message:"Entity not found"},404))}),J=async({catalogEntitiesService:s,ctx:t,catalogConfig:a,serverOutDir:o,store:n})=>g("com.redocly.catalogEntities.createEntity",async e=>{e?.setAttribute("object","catalogEntity");const d=t.get("logger");try{const r=await t.req.json();e?.setAttribute("requestBody",JSON.stringify(r));const i=D(r,a);if(!A({ctx:t,store:n,accessLevel:"WRITE",entityType:i.type,entityKey:i.key}))return e?.error(new Error("Access denied")),t.json({message:"Access denied"},403);const y=await s.transaction(async()=>{const u=await s.createEntity({entity:i,source:"remote",errorOnSkip:!0}),c=i.relations?.map(E=>O(i.key,E));return await s.createEntitiesRelations(c??[]),await j({ctx:t,catalogEntitiesService:s,addedEntities:u?[u]:void 0}),u});return y?(b(o),e?.setAttribute("id",y.id),e?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${y.id}`),e?.setAttribute("entity",JSON.stringify(y)),t.json(m(y))):(e?.error(new Error("Failed to create entity")),t.json({message:"Failed to create entity"},500))}catch(r){return d.error(r),r instanceof Error&&r.message.includes("validation failed")?(e?.error(new Error(r.message)),t.json({message:r.message},400)):(e?.error(r),t.json({message:"Failed to create entity"},500))}}),v=async({catalogEntitiesService:s,ctx:t,catalogConfig:a,serverOutDir:o,store:n})=>g("com.redocly.catalogEntities.bulkUpsertEntities",async e=>{e?.setAttribute("id","catalogEntityId"),e?.setAttribute("object","catalogEntity"),e?.setAttribute("uri","urn:redocly:realm:api:catalogEntity:catalogEntityId");const d=t.get("logger");try{const r=await t.req.json();e?.setAttribute("requestBody",JSON.stringify(r));const i=k(r,a);for(const u of i)if(!A({ctx:t,store:n,accessLevel:"WRITE",entityType:u.type,entityKey:u.key}))return e?.error(new Error("Access denied")),t.json({message:"Access denied"},403);const l=await s.transaction(async()=>{const u=await s.createEntities(i,"remote"),c=u.reduce((E,w)=>(w.status==="ok"&&w.resource!=null&&E.push(w.resource),E),[]);return await j({ctx:t,catalogEntitiesService:s,addedEntities:c}),u});if(!l.length)return e?.error(new Error("Failed to create entities")),t.json({message:"Failed to create entities"},500);b(o);const y=l.filter(u=>u.status==="ok");return e?.setAttribute("totalSuccess",y.length),e?.setAttribute("totalFailed",l.length-y.length),t.json(y.map(u=>({...u,resource:m(u.resource)})),207)}catch(r){return d.error(r),r instanceof Error&&r.message.includes("validation failed")?(e?.error(new Error(r.message)),t.json({message:r.message},400)):(e?.error(r),t.json({message:"Failed to create entities"},500))}}),B=async({catalogEntitiesService:s,ctx:t,catalogConfig:a,serverOutDir:o,store:n})=>g("com.redocly.catalogEntities.updateEntity",async e=>{e?.setAttribute("object","catalogEntity");const d=t.get("logger"),r=t.req.param(f);if(!r)return e?.error(new Error("Entity id is required")),t.json({message:"Entity id is required"},400);e?.setAttribute("id",r),e?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${r}`),e?.setAttribute("pathParams",JSON.stringify({entityId:r}));try{const i=await t.req.json();e?.setAttribute("requestBody",JSON.stringify(i));const l=await s.getEntityById(r);if(!l)return e?.error(new Error(`Entity with id: ${r} not found`)),t.json({message:`Entity with id: ${r} not found`},404);if(l.source==="file")return e?.error(new Error("File entities cannot be updated")),t.json({message:"File entities cannot be updated"},400);if(!A({ctx:t,store:n,accessLevel:"WRITE",entityType:i.type??l.type,entityKey:i.key??l.key}))return e?.error(new Error("Access denied")),t.json({message:"Access denied"},403);const u=P(i,a,i?.type??l.type),c=await s.updateEntity(u,l);return c?(b(o),e?.setAttribute("entity",JSON.stringify(c)),t.json(m(c))):(e?.error(new Error("Failed to update entity")),t.json({message:"Failed to update entity"},500))}catch(i){return d.error(i),i instanceof Error&&i.message.includes("validation failed")?(e?.error(new Error(i.message)),t.json({message:i.message},400)):(e?.error(i),t.json({message:i.message},500))}}),U=async({catalogEntitiesService:s,ctx:t,serverOutDir:a,store:o})=>g("com.redocly.catalogEntities.deleteEntity",async n=>{n?.setAttribute("object","catalogEntity");const e=t.get("logger"),d=t.req.param(f);if(!d)return n?.error(new Error("Entity id is required")),t.json({message:"Entity id is required"},400);n?.setAttribute("id",d),n?.setAttribute("uri",`urn:redocly:realm:api:catalogEntity:${d}`),n?.setAttribute("pathParams",JSON.stringify({entityId:d}));try{const r=await s.getEntityById(d);return r?r.source==="file"?(n?.error(new Error("File entities cannot be deleted")),t.json({message:"File entities cannot be deleted"},400)):A({ctx:t,store:o,accessLevel:"WRITE",entityType:r.type,entityKey:r.key})?await s.transaction(async()=>{const y=await s.deleteEntity(r);return y?(await j({ctx:t,catalogEntitiesService:s,removedEntities:r?[r]:void 0}),y):null})?(b(a),new Response(null,{status:204})):(n?.error(new Error("Failed to delete entity")),t.json({message:"Failed to delete entity"},500)):(n?.error(new Error("Access denied")),t.json({message:"Access denied"},403)):new Response(null,{status:204})}catch(r){return e.error(r),n?.error(r),t.json({message:"Failed to delete entity"},500)}}),_={GET:W,POST:J,PUT:v},$={GET:L,DELETE:U,PATCH:B};function st(s){return async t=>g("com.redocly.catalogEntities.accessed",async a=>{a?.setAttribute("id","catalogEntityId"),a?.setAttribute("object","catalogEntity"),a?.setAttribute("uri","urn:redocly:realm:api:catalogEntity:catalogEntityId");const o=await R.getInstance({baseDbDir:s.serverOutDir}),n=t.req.method;a?.setAttribute("method",n);const e=t.req.param(f)?$:_,d=T(s.config.entitiesCatalog),r=e[n];return r?await r({catalogEntitiesService:o,ctx:t,catalogConfig:d,serverOutDir:s.serverOutDir,store:s}):(a?.error(new Error("Method not allowed")),t.json({message:"Method not allowed"},405))})}export{st as catalogHandler};