@redocly/realm 0.136.0-next.1 → 0.136.0-next.2

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 (33) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/client/app/hooks/catalog/useCatalogViewMode.js +1 -1
  3. package/dist/client/app/hooks/catalog/useFetchCatalogEntities.js +1 -1
  4. package/dist/client/app/hooks/catalog/useFetchCatalogEntitiesRelations.js +1 -1
  5. package/dist/client/app/hooks/catalog/useSearchTracker.d.ts +2 -5
  6. package/dist/client/app/hooks/catalog/useSearchTracker.js +1 -1
  7. package/dist/server/api-routes/run-api-routes-worker.js +1 -1
  8. package/dist/server/config/env-config.d.ts +2 -0
  9. package/dist/server/config/env-schema.d.ts +5 -0
  10. package/dist/server/config/env-schemas/feature-flags.d.ts +4 -0
  11. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  12. package/dist/server/plugins/catalog-entities/database/catalog-entities-revisions-pruner.d.ts +2 -0
  13. package/dist/server/plugins/catalog-entities/database/catalog-entities-revisions-pruner.js +23 -3
  14. package/dist/server/plugins/catalog-entities/database/repositories/entities/entities-write-repository.js +1 -1
  15. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/base.d.ts +4 -1
  16. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/base.js +1 -1
  17. package/dist/server/plugins/catalog-entities/plugin.js +1 -1
  18. package/dist/server/plugins/markdown/markdoc/plugins/utils.d.ts +5 -0
  19. package/dist/server/plugins/markdown/markdoc/plugins/utils.js +1 -1
  20. package/dist/server/plugins/markdown/runtime-transform.js +1 -1
  21. package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +2 -0
  22. package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
  23. package/dist/server/store.js +1 -1
  24. package/dist/server/utils/sandbox-context.d.ts +18 -0
  25. package/dist/server/utils/sandbox-context.js +1 -0
  26. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +5 -4
  27. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  28. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  29. package/dist/server/workers/api-routes-worker-pool.js +1 -1
  30. package/dist/server/workers/mcp-tool-worker-pool.js +1 -1
  31. package/dist/server/workers/worker-pool.d.ts +1 -0
  32. package/dist/server/workers/worker-pool.js +1 -1
  33. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @redocly/realm
2
2
 
3
+ ## 0.136.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - 94b73e18acd: Added `numbered-list` and `item` Markdoc tags to render step-by-step guides with numbered, icon, or dot markers and deep-linkable item headlines.
8
+
9
+ ### Patch Changes
10
+
11
+ - 14597a56ac1: Fixed the built-in Docs MCP OAuth flow for public sites so they no longer advertise an authorization server or force a login.
12
+
13
+ Resolved the real `idpId` for protected sites using custom SSO.
14
+
15
+ - Updated dependencies [94b73e18acd]
16
+ - @redocly/theme@0.68.0-next.1
17
+ - @redocly/asyncapi-docs@1.13.0-next.2
18
+ - @redocly/graphql-docs@1.13.0-next.2
19
+ - @redocly/openapi-docs@3.24.0-next.2
20
+ - @redocly/portal-plugin-mock-server@0.21.0-next.2
21
+
3
22
  ## 0.136.0-next.1
4
23
 
5
24
  ### Patch Changes
@@ -1 +1 @@
1
- import{useState as i}from"react";import{telemetry as n}from"../../telemetry/index.js";const s="catalog-view-mode",w="table";function u(o){const[r,a]=i(o??w);return{viewMode:r,setViewMode:t=>{if(a(t),!(typeof window>"u")){try{localStorage.setItem(s,t)}catch(e){console.warn("Failed to save view mode preference to localStorage",e)}try{const e=new URL(window.location.href);e.searchParams.set("viewMode",t),window.history.replaceState({},"",e.toString()),n.sendCatalogEntitiesViewModeChangedMessage([{mode:t,id:t,object:"view_mode",uri:window.location.href}])}catch(e){console.warn("Failed to update viewMode query parameter",e)}}}}}export{u as useCatalogViewMode};
1
+ import{useRef as s,useState as c}from"react";import{generateBeforeAfterContext as d}from"@redocly/theme/core/utils";import{telemetry as w}from"../../telemetry/index.js";const l="catalog-view-mode",u="table";function m(a){const[o,n]=c(a??u),r=s(o);return{viewMode:o,setViewMode:t=>{const i=r.current;if(r.current=t,n(t),!(typeof window>"u")){try{localStorage.setItem(l,t)}catch(e){console.warn("Failed to save view mode preference to localStorage",e)}try{const e=new URL(window.location.href);e.searchParams.set("viewMode",t),window.history.replaceState({},"",e.toString()),w.sendCatalogEntitiesViewModeChangedMessage([...d("catalogEntitiesViewModeId","button",{mode:i},{mode:t})])}catch(e){console.warn("Failed to update viewMode query parameter",e)}}}}}export{m as useCatalogViewMode};
@@ -1 +1 @@
1
- import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as d,useRef as w}from"react";import{withPathPrefix as R}from"@redocly/theme/core/utils";import{getNextPageParam as S}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as F}from"./useSearchTracker";function x({limit:o=20,filter:i,sort:s="updated_at",search:n}={},r){const m=r?r.items.map(e=>e.key).join("-"):[],c=w(!0),l=c.current&&r;c.current&&(c.current=!1);const a=y({queryFn:async e=>{const t=new URLSearchParams;i&&t.append("filter",i),s&&t.append("sort",s),o&&t.append("limit",o.toString()),n&&t.append("search",n),e.pageParam&&Object.entries(e.pageParam).forEach(([P,f])=>{f!=null&&t.append(P,f.toString())});const p=new URL(R("/bff/catalog-entities"),window.location.origin);p.search=t.toString();const u=await fetch(p.toString());if(!u.ok)throw new Error(`Failed to fetch catalog entities from ${p.pathname}`);return u.json()},queryKey:["bff/catalog-entities",{limit:o,filter:i,sort:s,search:n,initialDataKeys:m}],initialData:l?{pages:[r],pageParams:[null]}:void 0,initialPageParam:null,getNextPageParam:S,refetchOnMount:!0,placeholderData:e=>e}),h=d(()=>{const e=a.data?.pages||[];return e[e.length-1]?.page.total},[a.data?.pages]),g=d(()=>(a.data?.pages||[]).flatMap(e=>e.items||[]),[a.data?.pages]);return F({isLoading:a.isLoading||a.isFetching,items:g,apiResource:"entities",searchQuery:n??""}),{query:a,items:g,total:h}}export{x as useFetchCatalogEntities};
1
+ import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as u,useRef as w}from"react";import{withPathPrefix as R}from"@redocly/theme/core/utils";import{getNextPageParam as S}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as F}from"./useSearchTracker";function x({limit:o=20,filter:i,sort:s="updated_at",search:n}={},r){const d=r?r.items.map(e=>e.key).join("-"):[],c=w(!0),m=c.current&&r;c.current&&(c.current=!1);const a=y({queryFn:async e=>{const t=new URLSearchParams;i&&t.append("filter",i),s&&t.append("sort",s),o&&t.append("limit",o.toString()),n&&t.append("search",n),e.pageParam&&Object.entries(e.pageParam).forEach(([P,f])=>{f!=null&&t.append(P,f.toString())});const p=new URL(R("/bff/catalog-entities"),window.location.origin);p.search=t.toString();const g=await fetch(p.toString());if(!g.ok)throw new Error(`Failed to fetch catalog entities from ${p.pathname}`);return g.json()},queryKey:["bff/catalog-entities",{limit:o,filter:i,sort:s,search:n,initialDataKeys:d}],initialData:m?{pages:[r],pageParams:[null]}:void 0,initialPageParam:null,getNextPageParam:S,refetchOnMount:!0,placeholderData:e=>e}),l=u(()=>{const e=a.data?.pages||[];return e[e.length-1]?.page.total},[a.data?.pages]),h=u(()=>(a.data?.pages||[]).flatMap(e=>e.items||[]),[a.data?.pages]);return F({isLoading:a.isLoading||a.isFetching,apiResource:"entities",searchQuery:n??""}),{query:a,items:h,total:l}}export{x as useFetchCatalogEntities};
@@ -1 +1 @@
1
- import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as m,useRef as S}from"react";import{withPathPrefix as F}from"@redocly/theme/core/utils";import{getNextPageParam as R}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as b}from"./useSearchTracker";function E({entityKey:g,limit:o=20,filter:i,sort:s="updated_at",search:r},n){const l=n?n.items.map(e=>e.key).join("-"):[],c=S(!0),h=c.current&&n;c.current&&(c.current=!1);const t=y({queryFn:async e=>{const a=new URLSearchParams;i&&a.append("filter",i),s&&a.append("sort",s),o&&a.append("limit",o.toString()),r&&a.append("search",r),e.pageParam&&Object.entries(e.pageParam).forEach(([w,f])=>{f!=null&&a.append(w,f.toString())});const p=new URL(F(`/bff/catalog-related-entities/${g}`),window.location.origin);p.search=a.toString();const d=await fetch(p.toString());if(!d.ok)throw new Error(`Failed to fetch catalog entity relations from ${p.pathname}`);return d.json()},queryKey:["bff/catalog-entities-relations",{limit:o,filter:i,sort:s,search:r,entityKey:g,initialDataKeys:l}],initialPageParam:null,initialData:h?{pages:[n],pageParams:[null]}:void 0,refetchOnMount:!0,getNextPageParam:R}),P=m(()=>{const e=t.data?.pages||[];return e[e.length-1]?.page?.total},[t.data?.pages]),u=m(()=>(t.data?.pages||[]).flatMap(e=>e.items||[]),[t.data?.pages]);return b({isLoading:t.isLoading||t.isFetching,items:u,apiResource:"related_entities",searchQuery:r??""}),{query:t,items:u,total:P}}export{E as useFetchCatalogEntitiesRelations};
1
+ import{useInfiniteQuery as y}from"@tanstack/react-query";import{useMemo as d,useRef as S}from"react";import{withPathPrefix as F}from"@redocly/theme/core/utils";import{getNextPageParam as R}from"../../../utils/catalog/get-next-page-param";import{useSearchTracker as b}from"./useSearchTracker";function E({entityKey:g,limit:o=20,filter:i,sort:s="updated_at",search:r},n){const m=n?n.items.map(e=>e.key).join("-"):[],c=S(!0),l=c.current&&n;c.current&&(c.current=!1);const t=y({queryFn:async e=>{const a=new URLSearchParams;i&&a.append("filter",i),s&&a.append("sort",s),o&&a.append("limit",o.toString()),r&&a.append("search",r),e.pageParam&&Object.entries(e.pageParam).forEach(([w,f])=>{f!=null&&a.append(w,f.toString())});const p=new URL(F(`/bff/catalog-related-entities/${g}`),window.location.origin);p.search=a.toString();const u=await fetch(p.toString());if(!u.ok)throw new Error(`Failed to fetch catalog entity relations from ${p.pathname}`);return u.json()},queryKey:["bff/catalog-entities-relations",{limit:o,filter:i,sort:s,search:r,entityKey:g,initialDataKeys:m}],initialPageParam:null,initialData:l?{pages:[n],pageParams:[null]}:void 0,refetchOnMount:!0,getNextPageParam:R}),h=d(()=>{const e=t.data?.pages||[];return e[e.length-1]?.page?.total},[t.data?.pages]),P=d(()=>(t.data?.pages||[]).flatMap(e=>e.items||[]),[t.data?.pages]);return b({isLoading:t.isLoading||t.isFetching,apiResource:"related_entities",searchQuery:r??""}),{query:t,items:P,total:h}}export{E as useFetchCatalogEntitiesRelations};
@@ -1,12 +1,9 @@
1
- import type { BffCatalogEntity } from '@redocly/theme/core/types';
2
- type BaseEntity = Pick<BffCatalogEntity, 'id' | 'key' | 'type'>;
3
1
  type ApiResource = 'entities' | 'related_entities';
4
- type SearchTrackerProps<T extends BaseEntity> = {
2
+ type SearchTrackerProps = {
5
3
  isLoading: boolean;
6
- items: T[];
7
4
  apiResource: ApiResource;
8
5
  searchQuery: string;
9
6
  };
10
- export declare function useSearchTracker<T extends BaseEntity>({ isLoading, items, apiResource, searchQuery }: SearchTrackerProps<T>): void;
7
+ export declare function useSearchTracker({ isLoading, apiResource, searchQuery }: SearchTrackerProps): void;
11
8
  export {};
12
9
  //# sourceMappingURL=useSearchTracker.d.ts.map
@@ -1 +1 @@
1
- import{useCallback as g,useEffect as m,useRef as f}from"react";import{useCatalogEntityDetails as d}from"@redocly/theme/core/hooks";import{usePageProps as p}from"../../../providers/hooks.js";import{telemetry as c}from"../../telemetry/index.js";const l={id:"redocly-unknown",object:"catalog_entity",uri:"redocly-unknown"},C={entities:"catalog_entity",related_entities:"catalog_entity"},E={entities:({items:e,searchQuery:n})=>{const t=e[0]??l;c.sendCatalogEntitiesListSearchQueryMessage([{query:n,...t}])},related_entities:({items:e,searchQuery:n})=>{const t=e[0]??l;c.sendCatalogEntitiesRelatedEntitiesListSearchQueryMessage([{query:n,...t}])}};function j({isLoading:e,items:n,apiResource:t,searchQuery:i}){const{catalogConfig:u,entitiesCatalogConfig:y}=p(),o=f(i),{getEntityDetailsLink:s}=d({catalogConfig:u,entitiesCatalogConfig:y}),a=g(r=>({id:r.id,object:C[t],uri:s(r)}),[s,t]);m(()=>{if(e)return;const r=i?.trim();!r||r===o.current||(o.current=i,E[t]({items:n.map(a),searchQuery:i}))},[n,e,i,t,a])}export{j as useSearchTracker};
1
+ import{useEffect as c,useRef as o}from"react";import{getBaseDataAttributes as a}from"@redocly/theme/core/utils";import{telemetry as n}from"../../telemetry/index.js";const l={entities:t=>{n.sendCatalogEntitiesListSearchQueryMessage([{...a("catalogEntitiesListSearchId","catalogEntitiesListSearch"),query:t}])},related_entities:t=>{n.sendCatalogEntitiesRelatedEntitiesListSearchQueryMessage([{...a("catalogEntitiesRelatedEntitiesListSearchId","catalogEntitiesRelatedEntitiesListSearch"),query:t}])}};function m({isLoading:t,apiResource:i,searchQuery:e}){const r=o(e);c(()=>{if(t)return;const s=e?.trim();!s||s===r.current||(r.current=e,l[i](e))},[t,e,i])}export{m as useSearchTracker};
@@ -1 +1 @@
1
- import*as p from"workerpool";import{withPathPrefix as f}from"@redocly/theme/core/utils";import{envConfig as l}from"../config/env-config.js";import{telemetry as a}from"../telemetry/index.js";import{TimeoutExceededError as E}from"./errors/timeout-exceeded.js";import{serializeRequest as _}from"./helpers/serialize-request.js";import{apiRoutesWorkers as A,API_ROUTES_WORKER_KEY as T}from"../workers/api-routes-worker-pool.js";import{API_FUNCTIONS_REQUEST_HANDLER_ID as u}from"../plugins/api-functions/index.js";const g=1e4,h=15;async function U(e,r,t){a.initialize();const s=r.get("auth"),i=await t.resolveRouteStaticData(e)||{},n=f(e.slug);try{const{status:o,headers:d,body:c}=await A.exec(T,[{serverOutDir:t.serverOutDir,slug:n,requestHandlerId:e.requestHandlerId,maxResponseSizeMB:h,ctxData:{user:{teams:s.teams,email:s.claims.email,claims:s.claims,idpAccessToken:s.idpAccessToken,idpId:s.claims.idpId,isAuthenticated:s.isAuthenticated},config:t.config,outdir:t.outdir,baseUrl:new URL(r.req.url).origin},staticData:{...i,props:{...i.props,routeSlug:n,outdir:t.outdir}},req:await _(r.req.raw),sqldRemoteDatabaseUrl:l.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:l.SQLD_REMOTE_DATABASE_AUTH_TOKEN}],{timeout:g});R({requestHandlerId:e.requestHandlerId,status:o,ctx:r});const m=c.data?Buffer.from(c.data):null;return new Response(m,{status:o,headers:d})}catch(o){throw M({requestHandlerId:e.requestHandlerId,error:o}),o instanceof p.Promise.TimeoutError?new E("Timeout exceeded"):o}}function R({requestHandlerId:e,status:r,ctx:t}){e.startsWith(u)?a.sendApiFunctionCalledMessage([{object:"api_function",error:null,handler:e,status:r}]):a.sendMockServerCalledMessage([{object:"mock_server",error:null,handler:e,headers:{"user-agent":t.req.header("user-agent")??null},status:r}])}function M({requestHandlerId:e,error:r}){const t={handler:e,message:r?.message??null,stack:r?.stack??null};e.startsWith(u)?a.sendApiFunctionErrorMessage([{object:"api_function",...t}]):a.sendMockServerErrorMessage([{object:"mock_server",...t}])}export{U as runApiRoutesWorker,M as sendTelemetryErrorMessage};
1
+ import*as p from"workerpool";import{withPathPrefix as E}from"@redocly/theme/core/utils";import{envConfig as u}from"../config/env-config.js";import{telemetry as a}from"../telemetry/index.js";import{TimeoutExceededError as f}from"./errors/timeout-exceeded.js";import{serializeRequest as g}from"./helpers/serialize-request.js";import{apiRoutesWorkers as A,API_ROUTES_WORKER_KEY as T}from"../workers/api-routes-worker-pool.js";import{API_FUNCTIONS_REQUEST_HANDLER_ID as d}from"../plugins/api-functions/index.js";const _=1e4,h=15;async function q(e,r,t){a.initialize();const i=r.get("auth"),o=await t.resolveRouteStaticData(e)||{},n=E(e.slug);try{const{status:s,headers:l,body:c}=await A.exec(T,[{serverOutDir:t.serverOutDir,slug:n,requestHandlerId:e.requestHandlerId,maxResponseSizeMB:h,ctxData:{user:{teams:i.teams,email:i.claims.email,claims:i.claims,idpAccessToken:i.idpAccessToken,idpId:i.claims.idpId,isAuthenticated:i.isAuthenticated},config:t.config,outdir:t.outdir,baseUrl:new URL(r.req.url).origin},staticData:{...o,props:{...o.props,routeSlug:n,outdir:t.outdir}},req:await g(r.req.raw),sqldRemoteDatabaseUrl:u.SQLD_REMOTE_DATABASE_URL,sqldRemoteDatabaseAuthToken:u.SQLD_REMOTE_DATABASE_AUTH_TOKEN}],{timeout:_});R({requestHandlerId:e.requestHandlerId,status:s,ctx:r});const m=c.data?Buffer.from(c.data):null;return new Response(m,{status:s,headers:l})}catch(s){throw I({requestHandlerId:e.requestHandlerId,error:s}),s instanceof p.Promise.TimeoutError?new f("Timeout exceeded"):s}}function R({requestHandlerId:e,status:r,ctx:t}){e.startsWith(d)?a.sendApiFunctionCalledMessage([{id:"apiFunctionId",object:"apiFunction",uri:"urn:redocly:realm:api:apiFunction:apiFunctionId",handler:e,status:r,message:null}]):a.sendMockServerCalledMessage([{object:"mock_server",error:null,handler:e,headers:{"user-agent":t.req.header("user-agent")??null},status:r}])}function I({requestHandlerId:e,error:r}){e.startsWith(d)?a.sendApiFunctionErrorMessage([{id:"apiFunctionId",object:"apiFunction",uri:"urn:redocly:realm:api:apiFunction:apiFunctionId",status:500,handler:e,message:r.message}]):a.sendMockServerErrorMessage([{object:"mock_server",handler:e,message:r.message}])}export{q as runApiRoutesWorker,I as sendTelemetryErrorMessage};
@@ -54,6 +54,7 @@ declare function loadEnvConfig(): {
54
54
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
55
55
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
56
56
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
57
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
57
58
  SEARCH_DEV_REINIT?: string | undefined;
58
59
  SEARCH_DEV_DEBUG: boolean;
59
60
  TYPESENSE_API_URL?: string | undefined;
@@ -135,6 +136,7 @@ export declare const envConfig: {
135
136
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
136
137
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
137
138
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
139
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
138
140
  SEARCH_DEV_REINIT?: string | undefined;
139
141
  SEARCH_DEV_DEBUG: boolean;
140
142
  TYPESENSE_API_URL?: string | undefined;
@@ -58,6 +58,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
58
58
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT: z.ZodOptional<z.ZodString>;
59
59
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
60
60
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
61
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
61
62
  } & {
62
63
  SEARCH_DEV_REINIT: z.ZodOptional<z.ZodString>;
63
64
  SEARCH_DEV_DEBUG: z.ZodEffects<z.ZodDefault<z.ZodEnum<["true", "false"]>>, boolean, "false" | "true" | undefined>;
@@ -136,6 +137,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
136
137
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
137
138
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
138
139
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
140
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
139
141
  SEARCH_DEV_REINIT?: string | undefined;
140
142
  SEARCH_DEV_DEBUG: boolean;
141
143
  TYPESENSE_API_URL?: string | undefined;
@@ -207,6 +209,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
207
209
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
208
210
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: "false" | "true" | undefined;
209
211
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: "false" | "true" | undefined;
212
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
210
213
  SEARCH_DEV_REINIT?: string | undefined;
211
214
  SEARCH_DEV_DEBUG?: "false" | "true" | undefined;
212
215
  TYPESENSE_API_URL?: string | undefined;
@@ -278,6 +281,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
278
281
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
279
282
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
280
283
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
284
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
281
285
  SEARCH_DEV_REINIT?: string | undefined;
282
286
  SEARCH_DEV_DEBUG: boolean;
283
287
  TYPESENSE_API_URL?: string | undefined;
@@ -349,6 +353,7 @@ export declare const envSchema: z.ZodEffects<z.ZodObject<{
349
353
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
350
354
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: "false" | "true" | undefined;
351
355
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: "false" | "true" | undefined;
356
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
352
357
  SEARCH_DEV_REINIT?: string | undefined;
353
358
  SEARCH_DEV_DEBUG?: "false" | "true" | undefined;
354
359
  TYPESENSE_API_URL?: string | undefined;
@@ -19,6 +19,8 @@ export declare const featureFlagsSchema: z.ZodObject<{
19
19
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
20
20
  /** Experimental: give the docs MCP `execute` sandbox a guarded `fetch` that can call gateway-eligible APIs. */
21
21
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
22
+ /** Escape hatch: disables the Node Permission Model sandbox on API function and MCP tool worker processes (hosted Reunite environments only). */
23
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX: z.ZodEffects<z.ZodOptional<z.ZodEnum<["true", "false"]>>, boolean | undefined, "false" | "true" | undefined>;
22
24
  }, "strip", z.ZodTypeAny, {
23
25
  NEW_CATALOG_ENABLED?: boolean | undefined;
24
26
  NEW_SCORECARDS_ENABLED?: boolean | undefined;
@@ -31,6 +33,7 @@ export declare const featureFlagsSchema: z.ZodObject<{
31
33
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
32
34
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: boolean | undefined;
33
35
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: boolean | undefined;
36
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: boolean | undefined;
34
37
  }, {
35
38
  NEW_CATALOG_ENABLED?: "false" | "true" | undefined;
36
39
  NEW_SCORECARDS_ENABLED?: "false" | "true" | undefined;
@@ -43,5 +46,6 @@ export declare const featureFlagsSchema: z.ZodObject<{
43
46
  REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT?: string | undefined;
44
47
  REDOCLY_EXP_MCP_CODE_MODE_ENABLED?: "false" | "true" | undefined;
45
48
  REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED?: "false" | "true" | undefined;
49
+ REDOCLY_DISABLE_API_FUNCTION_SANDBOX?: "false" | "true" | undefined;
46
50
  }>;
47
51
  //# sourceMappingURL=feature-flags.d.ts.map
@@ -1 +1 @@
1
- import{z as _}from"zod";import{envBooleanStringOptional as E}from"../../utils/envs/env-boolean-string.js";const D=_.object({NEW_CATALOG_ENABLED:E(),NEW_SCORECARDS_ENABLED:E(),ENABLE_COMMENTS:_.string().optional(),REDOCLY_EXP_DISABLE_MD_VALIDATION:E(),ENABLE_SOURCE_MAPS:E(),PLAN_GATES:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED:E(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT:_.string().optional(),REDOCLY_EXP_MCP_CODE_MODE_ENABLED:E(),REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED:E()});export{D as featureFlagsSchema};
1
+ import{z as _}from"zod";import{envBooleanStringOptional as E}from"../../utils/envs/env-boolean-string.js";const D=_.object({NEW_CATALOG_ENABLED:E(),NEW_SCORECARDS_ENABLED:E(),ENABLE_COMMENTS:_.string().optional(),REDOCLY_EXP_DISABLE_MD_VALIDATION:E(),ENABLE_SOURCE_MAPS:E(),PLAN_GATES:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_ENABLED:E(),REDOCLY_EXP_LLMSTXT_AGENT_FEEDBACK_URL:_.string().optional(),REDOCLY_EXP_LLMSTXT_AGENT_ENDPOINT:_.string().optional(),REDOCLY_EXP_MCP_CODE_MODE_ENABLED:E(),REDOCLY_EXP_MCP_GATEWAY_SERVER_ENABLED:E(),REDOCLY_DISABLE_API_FUNCTION_SANDBOX:E()});export{D as featureFlagsSchema};
@@ -2,6 +2,8 @@ import type { DatabaseConnection } from './types';
2
2
  export type PruneLocalRevisionsResult = {
3
3
  prunedEntities: number;
4
4
  prunedRelations: number;
5
+ legacyRelationsHealed: number;
6
+ legacyRelationsDeleted: number;
5
7
  affectedSourceFiles: string[];
6
8
  vacuumed: boolean;
7
9
  };
@@ -1,4 +1,24 @@
1
- import{and as h,eq as m,inArray as l,sql as S}from"drizzle-orm";import{entitiesRelationsTable as O}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as R}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{fileHashesTable as a}from"../../../providers/database/databases/sqlite-db/schemas/file-hashes-table.js";import{FileHashStatus as T}from"../../../persistence/file-hashes/types.js";import{DELETE_IDS_CHUNK_SIZE as d,VACUUM_AFTER_PRUNED_ROWS as w}from"./consts.js";const p={prunedEntities:0,prunedRelations:0,affectedSourceFiles:[],vacuumed:!1};class u{static async pruneLocalRevisions({localConnection:i,maxRevisions:r,vacuumThreshold:n=w}){if(!Number.isFinite(r)||r<=0)return p;const t=i.client.client,o=await t.transaction(async s=>{const c=await s.all(S`
1
+ import{and as U,eq as F,inArray as E,isNull as v,sql as a}from"drizzle-orm";import{entitiesRelationsTable as d}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as w}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{fileHashesTable as l}from"../../../providers/database/databases/sqlite-db/schemas/file-hashes-table.js";import{FileHashStatus as A}from"../../../persistence/file-hashes/types.js";import{DELETE_IDS_CHUNK_SIZE as _,VACUUM_AFTER_PRUNED_ROWS as g}from"./consts.js";const y={prunedEntities:0,prunedRelations:0,legacyRelationsHealed:0,legacyRelationsDeleted:0,affectedSourceFiles:[],vacuumed:!1};class c{static async pruneLocalRevisions({localConnection:n,maxRevisions:r,vacuumThreshold:s=g}){if(!Number.isFinite(r)||r<=0)return y;const t=n.client.client,S=await t.transaction(async i=>{const C=await i.all(a`
2
+ SELECT DISTINCT e.source_file
3
+ FROM entities_relations r
4
+ JOIN entities e ON e.key = r.source_key AND e.source_file IS NOT NULL
5
+ WHERE r.source_file IS NULL
6
+ `),h=await i.run(a`
7
+ UPDATE entities_relations
8
+ SET source_file = (
9
+ SELECT e.source_file FROM entities e
10
+ WHERE e.key = entities_relations.source_key
11
+ AND e.source_file IS NOT NULL
12
+ ORDER BY e.is_current DESC, e.revision DESC
13
+ LIMIT 1
14
+ )
15
+ WHERE source_file IS NULL
16
+ AND EXISTS (
17
+ SELECT 1 FROM entities e
18
+ WHERE e.key = entities_relations.source_key
19
+ AND e.source_file IS NOT NULL
20
+ )
21
+ `),N=Number(h.rowsAffected??0),k=await i.delete(d).where(v(d.sourceFile)).run(),T=Number(k.rowsAffected??0),u=await i.all(a`
2
22
  SELECT id, source_file
3
23
  FROM (
4
24
  SELECT id,
@@ -15,7 +35,7 @@ import{and as h,eq as m,inArray as l,sql as S}from"drizzle-orm";import{entitiesR
15
35
  WHERE revision_rank > ${r}
16
36
  AND COALESCE(is_current, 1) = 0
17
37
  AND COALESCE(is_default_version, 0) = 0
18
- `);if(c.length===0)return null;const f=new Set;for(const e of c)e.source_file&&f.add(e.source_file);for(const e of u.#e(c.map(_=>_.id),d))await s.delete(R).where(l(R.id,e)).run();const E=await s.all(S`
38
+ `),f=new Set(C.map(e=>e.source_file));for(const e of u)e.source_file&&f.add(e.source_file);if(u.length===0&&N===0&&T===0)return{legacyRelationsHealed:N,legacyRelationsDeleted:T,prune:null};for(const e of c.#e(u.map(O=>O.id),_))await i.delete(w).where(E(w.id,e)).run();const D=await i.all(a`
19
39
  SELECT id, source_file
20
40
  FROM entities_relations r
21
41
  WHERE r.source_file IS NOT NULL
@@ -39,4 +59,4 @@ import{and as h,eq as m,inArray as l,sql as S}from"drizzle-orm";import{entitiesR
39
59
  )
40
60
  )
41
61
  )
42
- `);for(const e of E)e.source_file&&f.add(e.source_file);for(const e of u.#e(E.map(_=>_.id),d))await s.delete(O).where(l(O.id,e)).run();const N=new Date().toISOString();for(const e of u.#e(Array.from(f),d))await s.update(a).set({status:T.OUTDATED,updatedAt:N}).where(l(a.filePath,e)).run();return{prunedEntities:c.length,prunedRelations:E.length,affectedSourceFiles:Array.from(f)}});if(!o)return p;const D=o.prunedEntities+o.prunedRelations;let A=!1;return D>=n&&(await t.run(S`VACUUM`),A=!0),{...o,vacuumed:A}}static async confirmPrunedFiles({localConnection:i,sourceFiles:r}){if(r.length===0)return;const n=i.client.client,t=new Date().toISOString();for(const o of u.#e(r,d))await n.update(a).set({status:T.UP_TO_DATE,updatedAt:t}).where(h(l(a.filePath,o),m(a.status,T.OUTDATED))).run()}static#e(i,r){const n=[];for(let t=0;t<i.length;t+=r)n.push(i.slice(t,t+r));return n}}export{u as CatalogEntitiesRevisionsPruner};
62
+ `);for(const e of D)e.source_file&&f.add(e.source_file);for(const e of c.#e(D.map(O=>O.id),_))await i.delete(d).where(E(d.id,e)).run();const I=new Date().toISOString();for(const e of c.#e(Array.from(f),_))await i.update(l).set({status:A.OUTDATED,updatedAt:I}).where(E(l.filePath,e)).run();return{legacyRelationsHealed:N,legacyRelationsDeleted:T,prune:{prunedEntities:u.length,prunedRelations:D.length,affectedSourceFiles:Array.from(f)}}}),{legacyRelationsHealed:p,legacyRelationsDeleted:R,prune:o}=S;if(!o&&p===0&&R===0)return y;const m=(o?.prunedEntities??0)+(o?.prunedRelations??0)+R;let L=!1;return m>=s&&(await t.run(a`VACUUM`),L=!0),{prunedEntities:o?.prunedEntities??0,prunedRelations:o?.prunedRelations??0,legacyRelationsHealed:p,legacyRelationsDeleted:R,affectedSourceFiles:o?.affectedSourceFiles??[],vacuumed:L}}static async confirmPrunedFiles({localConnection:n,sourceFiles:r}){if(r.length===0)return;const s=n.client.client,t=new Date().toISOString();for(const S of c.#e(r,_))await s.update(l).set({status:A.UP_TO_DATE,updatedAt:t}).where(U(E(l.filePath,S),F(l.status,A.OUTDATED))).run()}static#e(n,r){const s=[];for(let t=0;t<n.length;t+=r)s.push(n.slice(t,t+r));return s}}export{c as CatalogEntitiesRevisionsPruner};
@@ -1 +1 @@
1
- import{and as v,eq as c,isNull as E,or as T,sql as N}from"drizzle-orm";import{VERSION_NOT_SPECIFIED as I}from"@redocly/theme/core/constants";import{sha1 as V}from"../../../../../utils/crypto/sha1.js";import{envConfig as F}from"../../../../../config/env-config.js";import{entitiesTable as i}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{convertFilterToWhereCondition as K}from"../../../../../providers/database/pagination/filter.js";import{entitiesRelationsTable as a}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{RevisionRepository as x}from"../common/revision-repository.js";import{VersionRepository as z}from"../common/version-repository.js";import{EntityAttributesWriteRepository as H}from"../entityAttributes/entity-attributes-write-repository.js";import{createEntityDbRecord as O}from"../../mappers/create-entity-db-record.js";import{createEntityReadModel as k}from"../../mappers/create-entity-read-model.js";import{createEntityRelationDbRecordFromFileSchema as P}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{createEntityRelationDbRecordFromDto as j}from"../../mappers/create-entity-relation-db-record-from-dto.js";import{EntitiesReadRepository as G}from"./entities-read-repository.js";import{RelationsReadRepository as M}from"../relations/relations-read-repository.js";import{RelationsWriteRepository as $}from"../relations/relations-write-repository.js";const m=15;class ce{#e;#t;#n;#o;#i;#s;#a;#u;#r;constructor(e,s,r){this.#e=e,this.#i=s,this.#s=r,this.#t=new x(e),this.#n=new z(e),this.#o=new H(e),this.#a=new G(e),this.#u=new M(e),this.#r=new $(e,s,r)}async createEntity({entity:e,source:s,fileHash:r,sourceFile:o,isRootEntity:t,isDeleted:n,rbacTeams:l,errorOnSkip:d=!1,revision:h}){const{relations:u=[],...f}=e,y=V(JSON.stringify(f)),R=e.version??I,C=h??new Date().toISOString(),W=await this.#t.shouldSkipRevisionCreation(e.key,R,y,t,n);if(Array.isArray(l)&&await this.#o.upsertEntityAttributes({entityKey:e.key,rbacTeams:l,organizationId:this.#i,projectId:this.#s}),W){if(d)throw new Error("Entity validation failed: entity already exists");return null}const{shouldSetNewCurrentRevision:g,hasCurrentRevision:L}=await this.#t.getCurrentRevisionInfo({key:e.key,version:R,revision:C}),w=O({entity:{...e,revision:C,hash:y,isCurrent:g,isDefaultVersion:g,isDeleted:n,version:R},organizationId:this.#i,projectId:this.#s,source:s,sourceFile:o??null,fileHash:r??null}),{key:D,..._}=w;if(g&&L&&(await this.#t.markAllRevisionsAsNotCurrent(D),await this.#n.markAllVersionsAsNotDefault(D)),F.isDevelopMode&&!F.REDOCLY_INTERNAL_DEV)return await this.#l(w,u);const A=await this.#e.client.insert(i).values(w).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:_}).returning();return A.length?(u&&await this.#r.createEntityRelations(u.map(S=>({...S,sourceKey:e.key,targetKey:S.key}))),k(A[0])):null}async updateEntity(e,s){const{shouldSetNewCurrentRevision:r,hasCurrentRevision:o}=await this.#t.getCurrentRevisionInfo({key:s.key,version:e.version??s.version??I,revision:s.revision});r&&o&&(await this.#t.markAllRevisionsAsNotCurrent(s.key),await this.#n.markAllVersionsAsNotDefault(s.key));const t=O({entity:{...s,...e,hash:V(JSON.stringify({...s,...e})),isCurrent:r,isDefaultVersion:r,createdAt:s.createdAt??void 0},organizationId:this.#i,projectId:this.#s,source:"remote",sourceFile:null,fileHash:null}),{key:n,source:l,scorecardsStatus:d,...h}=t,u=await this.#e.client.insert(i).values(t).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:{...h,scorecardsStatus:N`CASE WHEN ${i.scorecardsStatus} = 'CALCULATING' THEN 'CANCELLED' ELSE 'OUTDATED' END`}}).returning();return u.length?k(u[0]):null}async setEntitiesAsOutdated(e){const s=K(e);await this.#e.client.update(i).set({scorecardsStatus:"OUTDATED"}).where(s)}async#l(e,s){const{key:r,source:o,version:t,...n}=e,h=await this.#e.client.select({id:i.id}).from(i).where(v(c(i.key,r),t?c(i.version,t):E(i.version))).limit(1).get()!=null?await this.#e.client.update(i).set(n).where(v(c(i.key,r),t?c(i.version,t):E(i.version))).returning():await this.#e.client.insert(i).values(e).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:n}).returning(),u=s?.map(f=>{const y=P({relation:f,sourceFile:e.sourceFile??"",fileHash:e.fileHash??"",sourceKey:e.key,sourceVersion:e.version??null,sourceRevision:e.revision??null,organizationId:this.#i,projectId:this.#s});return this.#e.client.insert(a).values(y).onConflictDoUpdate({target:[a.sourceKey,a.targetKey,a.sourceVersion,a.targetVersion,a.sourceRevision,a.targetRevision,a.sourceToTargetRelation],set:y}).run()})??[];return await p(u,m,async f=>f),k(h[0])}async softDeleteEntitiesWithRelations({filter:e,revision:s,fileHash:r}){const t={op:"AND",conditions:[e,{field:"is_deleted",operator:"equal",value:!1}]};for(;;){const n=await this.#a.getEntities({paginationParams:{filter:t,limit:500}});if(n.items.length===0)break;const l=await this.#c({entities:n.items,revision:s,fileHash:r});await this.#d(l,s)}}async deleteEntity(e){return await this.#r.deleteEntityRelationsOnEntityRemoval(e),await this.#e.client.delete(i).where(c(i.id,e.id)),await this.#t.ensureDefaultAndCurrentRevisionForKey(e.key),e.id}async deleteEntities(e){const s=K(e);if(!s)return!1;const r=await this.#e.client.delete(i).where(s).returning({key:i.key,source:i.source,isCurrent:i.isCurrent,isDefaultVersion:i.isDefaultVersion,version:i.version});if(r.length===0)return!0;const o=r.reduce((t,n)=>((n.isCurrent||n.isDefaultVersion)&&t.add(n.key),t),new Set);if(o.size===0)return!0;await p(Array.from(o),m,async t=>this.#t.ensureDefaultAndCurrentRevisionForKey(t));for(const t of r)await this.#e.client.delete(a).where(T(v(c(a.sourceKey,t.key),...t.version?[c(a.sourceVersion,t.version)]:[E(a.sourceVersion)]),v(c(a.targetKey,t.key),...t.version?[c(a.targetVersion,t.version)]:[E(a.targetVersion)])));return!0}async updateEntityScorecardsStatus(e,s){return(await this.#e.client.update(i).set({scorecardsStatus:s}).where(c(i.id,e)).returning()).length>0}async updateEntityScorecardsStatusIfCalculating(e,s){return(await this.#e.client.update(i).set({scorecardsStatus:s}).where(N`${i.id} = ${e} AND ${i.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}async#c({entities:e,revision:s,fileHash:r}){return(await p(e,m,async t=>{const n={type:t.type,key:t.key,title:t.title,summary:t.summary??void 0,tags:t.tags??void 0,metadata:t.metadata??void 0,git:t.git??void 0,contact:t.contact??void 0,links:t.links??void 0,version:t.version??void 0};return this.createEntity({entity:n,sourceFile:t.sourceFile??"",fileHash:r,isDeleted:!0,errorOnSkip:!1,source:t.source,revision:s})})).filter(t=>t!==null)}async#d(e,s){if(e.length===0)return!0;const r=await p(e,m,async o=>(await this.#u.getRelationsForEntity(o.key,o.version,s)).map(n=>{if(!n)return null;const l=n.direction,d=n.sourceToTargetRelation,h=n.targetKey,u=l==="outgoing"?o.key:h,f=l==="outgoing"?h:o.key,y=l==="outgoing"?d:d.startsWith("reverse:")?d.slice(8):d;return!y||!u||!f?null:j({type:y,sourceKey:u,targetKey:f,sourceVersion:o.version,targetVersion:o.version,sourceRevision:s,targetRevision:s,isDeleted:!0},this.#i,this.#s)}).filter(n=>n!==null));return await p(r.flat(),m,async o=>this.#r.upsertEntityRelation(o)),!0}}export{ce as EntitiesWriteRepository};
1
+ import{and as v,eq as c,isNull as E,or as x,sql as I}from"drizzle-orm";import{VERSION_NOT_SPECIFIED as V}from"@redocly/theme/core/constants";import{sha1 as F}from"../../../../../utils/crypto/sha1.js";import{envConfig as K}from"../../../../../config/env-config.js";import{entitiesTable as i}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{convertFilterToWhereCondition as O}from"../../../../../providers/database/pagination/filter.js";import{entitiesRelationsTable as a}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{RevisionRepository as z}from"../common/revision-repository.js";import{VersionRepository as H}from"../common/version-repository.js";import{EntityAttributesWriteRepository as P}from"../entityAttributes/entity-attributes-write-repository.js";import{createEntityDbRecord as W}from"../../mappers/create-entity-db-record.js";import{createEntityReadModel as k}from"../../mappers/create-entity-read-model.js";import{createEntityRelationDbRecordFromFileSchema as j}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{createEntityRelationDbRecordFromDto as G}from"../../mappers/create-entity-relation-db-record-from-dto.js";import{EntitiesReadRepository as M}from"./entities-read-repository.js";import{RelationsReadRepository as U}from"../relations/relations-read-repository.js";import{RelationsWriteRepository as $}from"../relations/relations-write-repository.js";const m=15;class de{#e;#t;#n;#o;#i;#s;#a;#l;#r;constructor(e,s,r){this.#e=e,this.#i=s,this.#s=r,this.#t=new z(e),this.#n=new H(e),this.#o=new P(e),this.#a=new M(e),this.#l=new U(e),this.#r=new $(e,s,r)}async createEntity({entity:e,source:s,fileHash:r,sourceFile:o,isRootEntity:t,isDeleted:n,rbacTeams:u,errorOnSkip:d=!1,revision:h}){const{relations:l=[],...f}=e,y=F(JSON.stringify(f)),g=e.version??V,C=h??new Date().toISOString(),L=await this.#t.shouldSkipRevisionCreation(e.key,g,y,t,n);Array.isArray(u)&&await this.#o.upsertEntityAttributes({entityKey:e.key,rbacTeams:u,organizationId:this.#i,projectId:this.#s});const D=K.isDevelopMode&&!K.REDOCLY_INTERNAL_DEV;if(l?.length&&!D&&await this.#r.createEntityRelations(l.map(N=>({...N,sourceKey:e.key,targetKey:N.key,sourceFile:o??null,fileHash:r??null}))),L){if(d)throw new Error("Entity validation failed: entity already exists");return null}const{shouldSetNewCurrentRevision:R,hasCurrentRevision:_}=await this.#t.getCurrentRevisionInfo({key:e.key,version:g,revision:C}),w=W({entity:{...e,revision:C,hash:y,isCurrent:R,isDefaultVersion:R,isDeleted:n,version:g},organizationId:this.#i,projectId:this.#s,source:s,sourceFile:o??null,fileHash:r??null}),{key:A,...T}=w;if(R&&_&&(await this.#t.markAllRevisionsAsNotCurrent(A),await this.#n.markAllVersionsAsNotDefault(A)),D)return await this.#u(w,l);const S=await this.#e.client.insert(i).values(w).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:T}).returning();return S.length?k(S[0]):null}async updateEntity(e,s){const{shouldSetNewCurrentRevision:r,hasCurrentRevision:o}=await this.#t.getCurrentRevisionInfo({key:s.key,version:e.version??s.version??V,revision:s.revision});r&&o&&(await this.#t.markAllRevisionsAsNotCurrent(s.key),await this.#n.markAllVersionsAsNotDefault(s.key));const t=W({entity:{...s,...e,hash:F(JSON.stringify({...s,...e})),isCurrent:r,isDefaultVersion:r,createdAt:s.createdAt??void 0},organizationId:this.#i,projectId:this.#s,source:"remote",sourceFile:null,fileHash:null}),{key:n,source:u,scorecardsStatus:d,...h}=t,l=await this.#e.client.insert(i).values(t).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:{...h,scorecardsStatus:I`CASE WHEN ${i.scorecardsStatus} = 'CALCULATING' THEN 'CANCELLED' ELSE 'OUTDATED' END`}}).returning();return l.length?k(l[0]):null}async setEntitiesAsOutdated(e){const s=O(e);await this.#e.client.update(i).set({scorecardsStatus:"OUTDATED"}).where(s)}async#u(e,s){const{key:r,source:o,version:t,...n}=e,h=await this.#e.client.select({id:i.id}).from(i).where(v(c(i.key,r),t?c(i.version,t):E(i.version))).limit(1).get()!=null?await this.#e.client.update(i).set(n).where(v(c(i.key,r),t?c(i.version,t):E(i.version))).returning():await this.#e.client.insert(i).values(e).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:n}).returning(),l=s?.map(f=>{const y=j({relation:f,sourceFile:e.sourceFile??"",fileHash:e.fileHash??"",sourceKey:e.key,sourceVersion:e.version??null,sourceRevision:e.revision??null,organizationId:this.#i,projectId:this.#s});return this.#e.client.insert(a).values(y).onConflictDoUpdate({target:[a.sourceKey,a.targetKey,a.sourceVersion,a.targetVersion,a.sourceRevision,a.targetRevision,a.sourceToTargetRelation],set:y}).run()})??[];return await p(l,m,async f=>f),k(h[0])}async softDeleteEntitiesWithRelations({filter:e,revision:s,fileHash:r}){const t={op:"AND",conditions:[e,{field:"is_deleted",operator:"equal",value:!1}]};for(;;){const n=await this.#a.getEntities({paginationParams:{filter:t,limit:500}});if(n.items.length===0)break;const u=await this.#c({entities:n.items,revision:s,fileHash:r});await this.#d(u,s)}}async deleteEntity(e){return await this.#r.deleteEntityRelationsOnEntityRemoval(e),await this.#e.client.delete(i).where(c(i.id,e.id)),await this.#t.ensureDefaultAndCurrentRevisionForKey(e.key),e.id}async deleteEntities(e){const s=O(e);if(!s)return!1;const r=await this.#e.client.delete(i).where(s).returning({key:i.key,source:i.source,isCurrent:i.isCurrent,isDefaultVersion:i.isDefaultVersion,version:i.version});if(r.length===0)return!0;const o=r.reduce((t,n)=>((n.isCurrent||n.isDefaultVersion)&&t.add(n.key),t),new Set);if(o.size===0)return!0;await p(Array.from(o),m,async t=>this.#t.ensureDefaultAndCurrentRevisionForKey(t));for(const t of r)await this.#e.client.delete(a).where(x(v(c(a.sourceKey,t.key),...t.version?[c(a.sourceVersion,t.version)]:[E(a.sourceVersion)]),v(c(a.targetKey,t.key),...t.version?[c(a.targetVersion,t.version)]:[E(a.targetVersion)])));return!0}async updateEntityScorecardsStatus(e,s){return(await this.#e.client.update(i).set({scorecardsStatus:s}).where(c(i.id,e)).returning()).length>0}async updateEntityScorecardsStatusIfCalculating(e,s){return(await this.#e.client.update(i).set({scorecardsStatus:s}).where(I`${i.id} = ${e} AND ${i.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}async#c({entities:e,revision:s,fileHash:r}){return(await p(e,m,async t=>{const n={type:t.type,key:t.key,title:t.title,summary:t.summary??void 0,tags:t.tags??void 0,metadata:t.metadata??void 0,git:t.git??void 0,contact:t.contact??void 0,links:t.links??void 0,version:t.version??void 0};return this.createEntity({entity:n,sourceFile:t.sourceFile??"",fileHash:r,isDeleted:!0,errorOnSkip:!1,source:t.source,revision:s})})).filter(t=>t!==null)}async#d(e,s){if(e.length===0)return!0;const r=await p(e,m,async o=>(await this.#l.getRelationsForEntity(o.key,o.version,s)).map(n=>{if(!n)return null;const u=n.direction,d=n.sourceToTargetRelation,h=n.targetKey,l=u==="outgoing"?o.key:h,f=u==="outgoing"?h:o.key,y=u==="outgoing"?d:d.startsWith("reverse:")?d.slice(8):d;return!y||!l||!f?null:G({type:y,sourceKey:l,targetKey:f,sourceVersion:o.version,targetVersion:o.version,sourceRevision:s,targetRevision:s,isDeleted:!0},this.#i,this.#s)}).filter(n=>n!==null));return await p(r.flat(),m,async o=>this.#r.upsertEntityRelation(o)),!0}}export{de as EntitiesWriteRepository};
@@ -5,7 +5,9 @@ import type { FileType } from '../../../../../persistence/file-hashes/types.js';
5
5
  import type { HashManager } from '../../../utils/hash-manager.js';
6
6
  import type { BaseApiEntitiesExtractorParams, SpecType } from '../../../types/extractors.js';
7
7
  import type { EntityDtoSchema } from '../../../schemas/dto-schemas.js';
8
- export declare abstract class BaseApiEntitiesExtractor<BundledApiDefinition extends Pick<FileInfo, 'realRelativePath' | 'relativePath' | 'isVirtual' | 'hash'>> implements BaseEntitiesExtractor {
8
+ export declare abstract class BaseApiEntitiesExtractor<BundledApiDefinition extends Pick<FileInfo, 'realRelativePath' | 'relativePath' | 'isVirtual' | 'hash'> & {
9
+ definition?: unknown;
10
+ }> implements BaseEntitiesExtractor {
9
11
  #private;
10
12
  protected type: 'api-description';
11
13
  protected specType: SpecType;
@@ -16,6 +18,7 @@ export declare abstract class BaseApiEntitiesExtractor<BundledApiDefinition exte
16
18
  protected fileHashManager: HashManager;
17
19
  protected entitySources: Record<string, string>;
18
20
  constructor(specType: SpecType, params: BaseApiEntitiesExtractorParams);
21
+ protected getDescriptionHash(description: BundledApiDefinition): string | undefined;
19
22
  extract(): Promise<void>;
20
23
  protected validateEntityRelationFileSchema(relation: unknown): void;
21
24
  protected getRbacTeamsForDefinition(relativePath: string): string[];
@@ -1 +1 @@
1
- import{entityRelationFileSchema as P}from"@redocly/config";import{removeLeadingSlash as _}from"@redocly/theme/core/utils";import{toKebabCase as R}from"../../../../../../utils/string/to-kebab-case.js";import{FileHashStatus as g}from"../../../../../persistence/file-hashes/types.js";import{promiseMapLimit as w}from"../../../../../utils/async/promise-map-limit.js";import{OPERATORS as A}from"../../../../../providers/database/pagination/constants.js";import{VERSION_NOT_SPECIFIED as T}from"@redocly/theme/core/constants";import{getRbacTeamsListForResource as F}from"../../../../../utils/rbac.js";import{envConfig as u}from"../../../../../config/env-config.js";import{resolveEntityVersion as S}from"../../../utils/resolve-entity-version.js";import{catalogDataCollector as p}from"../../../utils/catalog-data-collector.js";import{createValidator as D}from"../../../utils/ajv-validator.js";const H=15,C=200,O=500;class z{type="api-description";specType;fileType;actions;context;catalogEntitiesService;fileHashManager;entitySources={};#e;constructor(i,e){this.specType=i,this.fileType=e.fileType,this.actions=e.actions,this.context=e.context,this.catalogEntitiesService=e.catalogEntitiesService,this.fileHashManager=e.fileHashManager,this.#e=e.shouldCalculateEntities??!1}async extract(){const i=await this.loadApiDescriptions(),e=await this.fileHashManager.getStatusSnapshot(this.fileType);await this.fileHashManager.markAllAsOutdated(this.fileType),i.length&&p.addExtractor(this.specType);const a=this.#s(i),t={skipped:0,fileHashMissing:0,fileHashMismatch:0,shouldCalculateFlag:0,forceRevalidateFlag:0,previouslyUnconfirmed:0},o=[],r=new Set;await w(Array.from(a.values()),H,async c=>{for(const{description:s,version:l}of c)try{const n=s.hash;if(!n)continue;r.add(s.realRelativePath);const h=await this.fileHashManager.getByPath(s.realRelativePath),m=h?.hash!==n,v=e.get(s.realRelativePath),E=v!==void 0&&v!==g.UP_TO_DATE,f=this.#i({fileHashChanged:m,hasExistingHashRecord:!!h,previouslyUnconfirmed:E});if(!(f!==null)){t.skipped++,p.increaseSkippedFilesCount(),await this.fileHashManager.upsert(this.fileType,s.realRelativePath,n,g.UP_TO_DATE);continue}f&&t[f]++,f==="fileHashMismatch"&&h&&o.length<3&&o.push({path:s.realRelativePath,stored:h.hash,incoming:n});const d=await this.catalogEntitiesService.getEntityKeysAndVersionsBySourceFile(s.realRelativePath);await this.#a(d);const y=new Date().toISOString();await this.processApiDescription(s,y,l,d),(m||E||u.FORCE_CATALOG_CACHE_REVALIDATE)&&p.markSourceFileAsChanged(s.realRelativePath),await this.#r(d,s.realRelativePath,y,n),p.increaseProcessedFilesCount(),await this.fileHashManager.upsert(this.fileType,s.realRelativePath,n,g.OUTDATED),p.markFileHashPendingConfirmation({fileType:this.fileType,filePath:s.realRelativePath,hash:n})}catch(n){this.context.logger.warn(`Error extracting entities from ${this.specType} description: ${s.realRelativePath}`),this.context.logger.warn(n)}}),await this.#o(r)}#i({fileHashChanged:i,hasExistingHashRecord:e,previouslyUnconfirmed:a}){return i?e?"fileHashMismatch":"fileHashMissing":a?"previouslyUnconfirmed":this.#e?"shouldCalculateFlag":u.FORCE_CATALOG_CACHE_REVALIDATE?"forceRevalidateFlag":null}#a=async i=>{u.FORCE_CATALOG_CACHE_REVALIDATE&&(await this.catalogEntitiesService.deleteEntities({field:"key",operator:"in",value:Array.from(i).map(e=>e.split(":")[0])}),i.clear())};#t=async i=>{let e=0,a=0;for(;;){const t=await this.catalogEntitiesService.getEntities({paginationParams:{limit:O,filter:i}});if(!t||t.items.length===0)break;await this.catalogEntitiesService.deleteEntities({field:"id",operator:"in",value:t.items.map(({id:o})=>o)}),e+=t.items.length,a+=1}return{total:e,pages:a}};#s(i){const e=new Map;for(const a of i)if(!(a.isVirtual||!a.hash))try{const t=this.#n(a),o=this.mapApiDescriptionToEntity(a,t),r=e.get(o.key)??[];r.push({description:a,version:t}),e.set(o.key,r)}catch(t){this.context.logger.warn(`Error resolving entity key for ${this.specType} description (skipping): ${a.realRelativePath}`),this.context.logger.warn(t)}return e}#o=async i=>{const e=await this.fileHashManager.getAllOutdated(this.fileType);if(!e||e.length===0)return;const a=e.filter(({filePath:l})=>!i.has(l));if(a.length===0)return;a.forEach(({filePath:l})=>{p.markSourceFileAsRemoved(l)});const t=a.map(({filePath:l})=>l),{total:o,pages:r}=await this.#t({op:"AND",conditions:[{field:"source_file",operator:"in",value:t},{field:"is_current",operator:"equal",value:!0}]}),{total:c,pages:s}=await this.#t({field:"source_file",operator:"in",value:t});this.context.logger.info(`[${this.specType}] removed-files cleanup: files=${t.length} currentRevisionsDeleted=${o} currentPages=${r} orphanRevisionsDeleted=${c} orphanPages=${s} removedFiles=${t.slice(0,3).join(", ")}${t.length>3?`, +${t.length-3} more`:""}`)};#r=async(i,e,a,t)=>{if(i.size===0||u.FORCE_CATALOG_CACHE_REVALIDATE)return;const o=Array.from(i).map(r=>{const[c,s]=r.split(":");return{key:c,version:s}});for(let r=0;r<o.length;r+=C){const c=o.slice(r,r+C),s=Array.from(new Set(c.map(({key:n})=>n))),l=c.map(({key:n,version:h})=>({op:A.AND,conditions:[{field:"key",operator:"equal",value:n},{field:"version",operator:"equal",value:h}]}));await this.catalogEntitiesService.softDeleteEntitiesWithRelations({revision:a,fileHash:t,filter:{op:"AND",conditions:[{field:"key",operator:"in",value:s},{op:A.OR,conditions:l},{field:"source",operator:"equal",value:"file"},{field:"source_file",operator:"equal",value:e},{field:"is_current",operator:"equal",value:!0}]}})}};#n(i){const e=i.document?.info?.version??i.definition?.info?.version??null,a=S(e,i.realRelativePath);return a.success?a.version??T:T}validateEntityRelationFileSchema(i){D(P,{errorPrefix:"Entity relation validation failed:",dataVar:"relation"})(i)}getRbacTeamsForDefinition(i){const e=this.actions.getConfig().access?.rbac,a=this.actions.getRouteByFsPath(i);return F(a||{fsPath:i},e||{})}resolveEntityKey({realRelativePath:i,customKey:e,extensionPattern:a}){const t=this.#l(i,a);return typeof e=="string"&&e.trim()?R(e.trim()):t}#l(i,e){let t=_(i);for(;/^@[^/]+\//.test(t);)t=t.replace(/^@[^/]+\//,"");t=t.replace(/\/@[^/]+/g,"");const o=e?t.replace(e,""):t.replace(/\.[^.]+$/,"");return R(o.replace(/[\\/]/g,"-"))}}export{z as BaseApiEntitiesExtractor};
1
+ import{entityRelationFileSchema as P}from"@redocly/config";import{removeLeadingSlash as _}from"@redocly/theme/core/utils";import{sha1 as S}from"../../../../../utils/crypto/sha1.js";import{toKebabCase as R}from"../../../../../../utils/string/to-kebab-case.js";import{FileHashStatus as g}from"../../../../../persistence/file-hashes/types.js";import{promiseMapLimit as w}from"../../../../../utils/async/promise-map-limit.js";import{OPERATORS as A}from"../../../../../providers/database/pagination/constants.js";import{VERSION_NOT_SPECIFIED as T}from"@redocly/theme/core/constants";import{getRbacTeamsListForResource as D}from"../../../../../utils/rbac.js";import{envConfig as u}from"../../../../../config/env-config.js";import{resolveEntityVersion as F}from"../../../utils/resolve-entity-version.js";import{catalogDataCollector as p}from"../../../utils/catalog-data-collector.js";import{createValidator as H}from"../../../utils/ajv-validator.js";const O=15,C=200,k=500;class Y{type="api-description";specType;fileType;actions;context;catalogEntitiesService;fileHashManager;entitySources={};#e;constructor(t,e){this.specType=t,this.fileType=e.fileType,this.actions=e.actions,this.context=e.context,this.catalogEntitiesService=e.catalogEntitiesService,this.fileHashManager=e.fileHashManager,this.#e=e.shouldCalculateEntities??!1}getDescriptionHash(t){return t.definition===void 0?t.hash:S(JSON.stringify(t.definition))}async extract(){const t=await this.loadApiDescriptions(),e=await this.fileHashManager.getStatusSnapshot(this.fileType);await this.fileHashManager.markAllAsOutdated(this.fileType),t.length&&p.addExtractor(this.specType);const a=this.#s(t),i={skipped:0,fileHashMissing:0,fileHashMismatch:0,shouldCalculateFlag:0,forceRevalidateFlag:0,previouslyUnconfirmed:0},o=[],r=new Set;await w(Array.from(a.values()),O,async c=>{for(const{description:s,version:l}of c)try{const n=this.getDescriptionHash(s);if(!n)continue;r.add(s.realRelativePath);const h=await this.fileHashManager.getByPath(s.realRelativePath),m=h?.hash!==n,v=e.get(s.realRelativePath),y=v!==void 0&&v!==g.UP_TO_DATE,f=this.#i({fileHashChanged:m,hasExistingHashRecord:!!h,previouslyUnconfirmed:y});if(!(f!==null)){i.skipped++,p.increaseSkippedFilesCount(),await this.fileHashManager.upsert(this.fileType,s.realRelativePath,n,g.UP_TO_DATE);continue}f&&i[f]++,f==="fileHashMismatch"&&h&&o.length<3&&o.push({path:s.realRelativePath,stored:h.hash,incoming:n});const d=await this.catalogEntitiesService.getEntityKeysAndVersionsBySourceFile(s.realRelativePath);await this.#a(d);const E=new Date().toISOString();await this.processApiDescription(s,E,l,d),(m||y||u.FORCE_CATALOG_CACHE_REVALIDATE)&&p.markSourceFileAsChanged(s.realRelativePath),await this.#r(d,s.realRelativePath,E,n),p.increaseProcessedFilesCount(),await this.fileHashManager.upsert(this.fileType,s.realRelativePath,n,g.OUTDATED),p.markFileHashPendingConfirmation({fileType:this.fileType,filePath:s.realRelativePath,hash:n})}catch(n){this.context.logger.warn(`Error extracting entities from ${this.specType} description: ${s.realRelativePath}`),this.context.logger.warn(n)}}),await this.#o(r)}#i({fileHashChanged:t,hasExistingHashRecord:e,previouslyUnconfirmed:a}){return t?e?"fileHashMismatch":"fileHashMissing":a?"previouslyUnconfirmed":this.#e?"shouldCalculateFlag":u.FORCE_CATALOG_CACHE_REVALIDATE?"forceRevalidateFlag":null}#a=async t=>{u.FORCE_CATALOG_CACHE_REVALIDATE&&(await this.catalogEntitiesService.deleteEntities({field:"key",operator:"in",value:Array.from(t).map(e=>e.split(":")[0])}),t.clear())};#t=async t=>{let e=0,a=0;for(;;){const i=await this.catalogEntitiesService.getEntities({paginationParams:{limit:k,filter:t}});if(!i||i.items.length===0)break;await this.catalogEntitiesService.deleteEntities({field:"id",operator:"in",value:i.items.map(({id:o})=>o)}),e+=i.items.length,a+=1}return{total:e,pages:a}};#s(t){const e=new Map;for(const a of t)if(!(a.isVirtual||!a.hash))try{const i=this.#n(a),o=this.mapApiDescriptionToEntity(a,i),r=e.get(o.key)??[];r.push({description:a,version:i}),e.set(o.key,r)}catch(i){this.context.logger.warn(`Error resolving entity key for ${this.specType} description (skipping): ${a.realRelativePath}`),this.context.logger.warn(i)}return e}#o=async t=>{const e=await this.fileHashManager.getAllOutdated(this.fileType);if(!e||e.length===0)return;const a=e.filter(({filePath:l})=>!t.has(l));if(a.length===0)return;a.forEach(({filePath:l})=>{p.markSourceFileAsRemoved(l)});const i=a.map(({filePath:l})=>l),{total:o,pages:r}=await this.#t({op:"AND",conditions:[{field:"source_file",operator:"in",value:i},{field:"is_current",operator:"equal",value:!0}]}),{total:c,pages:s}=await this.#t({field:"source_file",operator:"in",value:i});this.context.logger.info(`[${this.specType}] removed-files cleanup: files=${i.length} currentRevisionsDeleted=${o} currentPages=${r} orphanRevisionsDeleted=${c} orphanPages=${s} removedFiles=${i.slice(0,3).join(", ")}${i.length>3?`, +${i.length-3} more`:""}`)};#r=async(t,e,a,i)=>{if(t.size===0||u.FORCE_CATALOG_CACHE_REVALIDATE)return;const o=Array.from(t).map(r=>{const[c,s]=r.split(":");return{key:c,version:s}});for(let r=0;r<o.length;r+=C){const c=o.slice(r,r+C),s=Array.from(new Set(c.map(({key:n})=>n))),l=c.map(({key:n,version:h})=>({op:A.AND,conditions:[{field:"key",operator:"equal",value:n},{field:"version",operator:"equal",value:h}]}));await this.catalogEntitiesService.softDeleteEntitiesWithRelations({revision:a,fileHash:i,filter:{op:"AND",conditions:[{field:"key",operator:"in",value:s},{op:A.OR,conditions:l},{field:"source",operator:"equal",value:"file"},{field:"source_file",operator:"equal",value:e},{field:"is_current",operator:"equal",value:!0}]}})}};#n(t){const e=t.document?.info?.version??t.definition?.info?.version??null,a=F(e,t.realRelativePath);return a.success?a.version??T:T}validateEntityRelationFileSchema(t){H(P,{errorPrefix:"Entity relation validation failed:",dataVar:"relation"})(t)}getRbacTeamsForDefinition(t){const e=this.actions.getConfig().access?.rbac,a=this.actions.getRouteByFsPath(t);return D(a||{fsPath:t},e||{})}resolveEntityKey({realRelativePath:t,customKey:e,extensionPattern:a}){const i=this.#l(t,a);return typeof e=="string"&&e.trim()?R(e.trim()):i}#l(t,e){let i=_(t);for(;/^@[^/]+\//.test(i);)i=i.replace(/^@[^/]+\//,"");i=i.replace(/\/@[^/]+/g,"");const o=e?i.replace(e,""):i.replace(/\.[^.]+$/,"");return R(o.replace(/[\\/]/g,"-"))}}export{Y as BaseApiEntitiesExtractor};
@@ -1 +1 @@
1
- import{FileHashStatus as v,FileType as h}from"../../persistence/file-hashes/types.js";import{envConfig as C}from"../../config/env-config.js";import{CATALOG_BASE_SLUG as S}from"../../../constants/catalog-entities.js";import{telemetryTraceStep as k}from"../../../cli/telemetry/helpers/trace-step.js";import{catalogDataCollector as f}from"./utils/catalog-data-collector.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as B,ENTITIES_MAP_GLOBAL_DATA_KEY as $}from"../../constants/plugins/catalog-entities.js";import{CacheService as z}from"../../persistence/cache/services/cache-service.js";import{getTemplatePath as O}from"./utils/get-template-path.js";import{getCompleteCatalogConfig as _}from"./get-complete-catalog-config.js";import{AsyncApiEntitiesExtractor as Y}from"./extensions/extractors/api-description/asyncapi-entities-extractor.js";import{GraphqlEntitiesExtractor as U}from"./extensions/extractors/api-description/graphql-entities-extractor.js";import{FileHashesService as j}from"../../persistence/file-hashes/services/file-hashes-service.js";import{CatalogEntitiesService as q}from"./database/catalog-entities-service.js";import{CatalogEntitiesPublisher as V}from"./database/catalog-entities-publisher.js";import{CatalogEntitiesRevisionsPruner as I}from"./database/catalog-entities-revisions-pruner.js";import{DEFAULT_MAX_ENTITY_REVISIONS as W}from"./database/consts.js";import{DatabaseConnectionFactory as J}from"../../providers/database/database-connection-factory.js";import{ArazzoEntitiesExtractor as X}from"./extensions/extractors/api-description/arazzo-entities-extractor.js";import{FsEntitiesExtractor as Z}from"./extensions/extractors/fs-entities-extractor.js";import{HashManager as K}from"./utils/hash-manager.js";import{RbacConfigHashCache as Q}from"./utils/rbac-config-hash-cache.js";import{OpenApiEntitiesExtractor as ee}from"./extensions/extractors/api-description/openapi-entities-extractor.js";const te="catalog-entity-template",ie="catalog-entity";let w=!0;async function ae(){return{id:"CatalogEntities",requiredEntitlements:["catalog"],async processContent(e,t){const s=await t.getConfig(),r=_(s.entitiesCatalog);if(!r.show)return;const{logger:n}=t,a=e.registerServerPropsGetter(ie,O("../get-server-props.js")),g=e.createTemplate(te,O("../template/index.js"));e.addRoute({duplicateInAllLocales:!0,slug:S,fsPath:"",templateId:g,excludeFromSidebar:!0,hasClientRoutes:!0,serverPropsGetterIds:[a],getNavText:()=>Promise.resolve("Catalog"),getStaticData:async()=>({props:{catalogConfig:r}})});const[l]=Object.entries(r.catalogs??{}).find(([P,m])=>!m?.hide)||[];l&&e.addRedirect(S,{type:302,to:`${S}/${l}`},{trackOriginalSource:!1}),n.info("Catalog Entities plugin finished")},async afterRoutesCreated(e,t){await k("build.plugin.catalog_entities",async s=>{const r=await t.getConfig(),n=_(r.entitiesCatalog);if(s?.setAttribute("config",JSON.stringify(n)),!n.show)return;const{logger:a}=t;f.resetForRun();const g=w&&C.isDevelopMode,l=await z.getInstance({baseDbDir:e.serverOutDir,databaseType:"local"}),P=new Q(l),{rbacConfigChanged:m}=await P.syncAndDetectChange(r.access?.rbac??null),c=g||m,o=await q.getInstance({baseDbDir:e.serverOutDir,removeExisting:g,databaseType:"local",runWithPragmaWalWriteOptimization:!0}),T=await j.getInstance({baseDbDir:e.serverOutDir,databaseType:"local"}),u=new K(T),F=[new Z({fileHashManager:u,context:t,catalogEntitiesService:o,catalogConfig:n,shouldCalculateEntities:c}),new ee({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.OPENAPI_DESCRIPTION,shouldCalculateEntities:c}),new Y({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.ASYNCAPI_DESCRIPTION,shouldCalculateEntities:c}),new U({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.GRAPHQL_DESCRIPTION,shouldCalculateEntities:c}),new X({actions:e,context:t,catalogEntitiesService:o,fileHashManager:u,fileType:h.ARAZZO_DESCRIPTION,shouldCalculateEntities:c})];a.info("Starting entities extractors...");const R=a.startTiming();await o.transaction(async()=>{await Promise.all(F.map(async i=>i.extract()))});const N=o.getEntitySources();e.setGlobalData({[$]:N}),await l.deleteByNamespace(B),a.infoTime(R,"Entities extractors finished");const b=C.CATALOG_MAX_REVISIONS??W,d=await J.create({baseDbDir:e.serverOutDir,databaseType:"local"}),H=!C.isDevelopMode&&C.isProductionEnv;let E=[];if(H&&d&&b>0){const i=await I.pruneLocalRevisions({localConnection:d,maxRevisions:b});if(i.prunedEntities>0||i.prunedRelations>0){a.info(`Pruned entity revision history: entities=${i.prunedEntities} relations=${i.prunedRelations} files=${i.affectedSourceFiles.length} (keeping ${b} revisions per entity)`),E=i.affectedSourceFiles;for(const D of E)f.markSourceFileAsChanged(D)}}const{changedSourceFiles:L,removedSourceFiles:A}=f.getPublishDelta(),x=a.startTiming(),y=await V.publishFromLocalToRemote(m?{baseDbDir:e.serverOutDir}:{baseDbDir:e.serverOutDir,changedSourceFiles:L,removedSourceFiles:A});if(y.published&&a.infoTime(x,"Entities published to the database"),y.shouldFinalizeFileHashes){A.length&&await T.deleteFileHashes({op:"AND",conditions:[{field:"status",operator:"equal",value:v.OUTDATED},{field:"file_path",operator:"in",value:A}]});const i=f.getFileHashConfirmations();for(const{fileType:D,filePath:G,hash:M}of i)await T.upsertFileHash({fileType:D,filePath:G,hash:M,status:v.UP_TO_DATE});y.published&&E.length&&d&&await I.confirmPrunedFiles({localConnection:d,sourceFiles:E})}else a.warn("Skipped file_hashes finalization because remote catalog publish connection is unavailable; next build will retry.");const p=await f.getCatalogEntitiesData(o);s?.setAttribute("totalEntities",p.totalEntitiesCount),s?.setAttribute("entitiesCountByType",JSON.stringify(p.countOfEntitiesByType)),s?.setAttribute("totalFilesSkippedByHash",p.totalFilesSkippedByHash),s?.setAttribute("totalProcessedFiles",p.totalProcessedFiles),s?.setAttribute("extractors",p.extractors),w=!1})}}}var ve=ae;export{ae as catalogEntitiesPlugin,ve as default};
1
+ import{FileHashStatus as v,FileType as h}from"../../persistence/file-hashes/types.js";import{envConfig as C}from"../../config/env-config.js";import{CATALOG_BASE_SLUG as S}from"../../../constants/catalog-entities.js";import{telemetryTraceStep as $}from"../../../cli/telemetry/helpers/trace-step.js";import{catalogDataCollector as f}from"./utils/catalog-data-collector.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as k,ENTITIES_MAP_GLOBAL_DATA_KEY as B}from"../../constants/plugins/catalog-entities.js";import{CacheService as z}from"../../persistence/cache/services/cache-service.js";import{getTemplatePath as R}from"./utils/get-template-path.js";import{getCompleteCatalogConfig as O}from"./get-complete-catalog-config.js";import{AsyncApiEntitiesExtractor as Y}from"./extensions/extractors/api-description/asyncapi-entities-extractor.js";import{GraphqlEntitiesExtractor as U}from"./extensions/extractors/api-description/graphql-entities-extractor.js";import{FileHashesService as j}from"../../persistence/file-hashes/services/file-hashes-service.js";import{CatalogEntitiesService as q}from"./database/catalog-entities-service.js";import{CatalogEntitiesPublisher as V}from"./database/catalog-entities-publisher.js";import{CatalogEntitiesRevisionsPruner as _}from"./database/catalog-entities-revisions-pruner.js";import{DEFAULT_MAX_ENTITY_REVISIONS as W}from"./database/consts.js";import{DatabaseConnectionFactory as J}from"../../providers/database/database-connection-factory.js";import{ArazzoEntitiesExtractor as X}from"./extensions/extractors/api-description/arazzo-entities-extractor.js";import{FsEntitiesExtractor as Z}from"./extensions/extractors/fs-entities-extractor.js";import{HashManager as K}from"./utils/hash-manager.js";import{RbacConfigHashCache as Q}from"./utils/rbac-config-hash-cache.js";import{OpenApiEntitiesExtractor as ee}from"./extensions/extractors/api-description/openapi-entities-extractor.js";const te="catalog-entity-template",ie="catalog-entity";let I=!0;async function ae(){return{id:"CatalogEntities",requiredEntitlements:["catalog"],async processContent(e,i){const s=await i.getConfig(),r=O(s.entitiesCatalog);if(!r.show)return;const{logger:n}=i,a=e.registerServerPropsGetter(ie,R("../get-server-props.js")),g=e.createTemplate(te,R("../template/index.js"));e.addRoute({duplicateInAllLocales:!0,slug:S,fsPath:"",templateId:g,excludeFromSidebar:!0,hasClientRoutes:!0,serverPropsGetterIds:[a],getNavText:()=>Promise.resolve("Catalog"),getStaticData:async()=>({props:{catalogConfig:r}})});const[l]=Object.entries(r.catalogs??{}).find(([P,m])=>!m?.hide)||[];l&&e.addRedirect(S,{type:302,to:`${S}/${l}`},{trackOriginalSource:!1}),n.info("Catalog Entities plugin finished")},async afterRoutesCreated(e,i){await $("build.plugin.catalog_entities",async s=>{const r=await i.getConfig(),n=O(r.entitiesCatalog);if(s?.setAttribute("config",JSON.stringify(n)),!n.show)return;const{logger:a}=i;f.resetForRun();const g=I&&C.isDevelopMode,l=await z.getInstance({baseDbDir:e.serverOutDir,databaseType:"local"}),P=new Q(l),{rbacConfigChanged:m}=await P.syncAndDetectChange(r.access?.rbac??null),c=g||m,o=await q.getInstance({baseDbDir:e.serverOutDir,removeExisting:g,databaseType:"local",runWithPragmaWalWriteOptimization:!0}),T=await j.getInstance({baseDbDir:e.serverOutDir,databaseType:"local"}),u=new K(T),w=[new Z({fileHashManager:u,context:i,catalogEntitiesService:o,catalogConfig:n,shouldCalculateEntities:c}),new ee({actions:e,context:i,catalogEntitiesService:o,fileHashManager:u,fileType:h.OPENAPI_DESCRIPTION,shouldCalculateEntities:c}),new Y({actions:e,context:i,catalogEntitiesService:o,fileHashManager:u,fileType:h.ASYNCAPI_DESCRIPTION,shouldCalculateEntities:c}),new U({actions:e,context:i,catalogEntitiesService:o,fileHashManager:u,fileType:h.GRAPHQL_DESCRIPTION,shouldCalculateEntities:c}),new X({actions:e,context:i,catalogEntitiesService:o,fileHashManager:u,fileType:h.ARAZZO_DESCRIPTION,shouldCalculateEntities:c})];a.info("Starting entities extractors...");const F=a.startTiming();await o.transaction(async()=>{await Promise.all(w.map(async t=>t.extract()))});const H=o.getEntitySources();e.setGlobalData({[B]:H}),await l.deleteByNamespace(k),a.infoTime(F,"Entities extractors finished");const y=C.CATALOG_MAX_REVISIONS??W,d=await J.create({baseDbDir:e.serverOutDir,databaseType:"local"}),N=!C.isDevelopMode&&C.isProductionEnv;let E=[];if(N&&d&&y>0){const t=await _.pruneLocalRevisions({localConnection:d,maxRevisions:y});if(t.prunedEntities>0||t.prunedRelations>0||t.legacyRelationsHealed>0||t.legacyRelationsDeleted>0){a.info(`Pruned entity revision history: entities=${t.prunedEntities} relations=${t.prunedRelations} legacyHealed=${t.legacyRelationsHealed} legacyDeleted=${t.legacyRelationsDeleted} files=${t.affectedSourceFiles.length} (keeping ${y} revisions per entity)`),E=t.affectedSourceFiles;for(const D of E)f.markSourceFileAsChanged(D)}}const{changedSourceFiles:L,removedSourceFiles:b}=f.getPublishDelta(),x=a.startTiming(),A=await V.publishFromLocalToRemote(m?{baseDbDir:e.serverOutDir}:{baseDbDir:e.serverOutDir,changedSourceFiles:L,removedSourceFiles:b});if(A.published&&a.infoTime(x,"Entities published to the database"),A.shouldFinalizeFileHashes){b.length&&await T.deleteFileHashes({op:"AND",conditions:[{field:"status",operator:"equal",value:v.OUTDATED},{field:"file_path",operator:"in",value:b}]});const t=f.getFileHashConfirmations();for(const{fileType:D,filePath:G,hash:M}of t)await T.upsertFileHash({fileType:D,filePath:G,hash:M,status:v.UP_TO_DATE});A.published&&E.length&&d&&await _.confirmPrunedFiles({localConnection:d,sourceFiles:E})}else a.warn("Skipped file_hashes finalization because remote catalog publish connection is unavailable; next build will retry.");const p=await f.getCatalogEntitiesData(o);s?.setAttribute("totalEntities",p.totalEntitiesCount),s?.setAttribute("entitiesCountByType",JSON.stringify(p.countOfEntitiesByType)),s?.setAttribute("totalFilesSkippedByHash",p.totalFilesSkippedByHash),s?.setAttribute("totalProcessedFiles",p.totalProcessedFiles),s?.setAttribute("extractors",p.extractors),I=!1})}}}var ve=ae;export{ae as catalogEntitiesPlugin,ve as default};
@@ -4,6 +4,11 @@ export declare function getHeadingData(node: Tag | Node): {
4
4
  id: any;
5
5
  depth: any;
6
6
  } | undefined;
7
+ export declare function getNumberedItemHeadingData(node: Tag | Node): {
8
+ value: string;
9
+ id: any;
10
+ depth: any;
11
+ } | undefined;
7
12
  export declare function extractFirstHeading(ast: Node): undefined | string;
8
13
  export declare const EXIT: unique symbol;
9
14
  /**
@@ -1 +1 @@
1
- import{isNode as l}from"../../../../../markdoc/helpers/guards/is-node.js";import{isStringNode as s}from"../../../../../markdoc/helpers/guards/is-string-node.js";import{getNodeAttribute as c}from"../../../../../markdoc/helpers/get-node-attribute.js";import{getInnerText as a}from"../../../../../markdoc/helpers/get-inner-text.js";function p(t){const r=c(t,"level");if(r)return{value:a(t.children),id:c(t,"id"),depth:r}}function h(t){let r;return f(t,i=>{if(l(i)&&i.type==="heading")return r=p(i)?.value,o}),r}const o=Symbol("Exit visitor");function f(t,r){if(!t)return;const i=r(t);if(i===o)return i;for(const e of t.children){if(!e||s(e))continue;const n=f(e,r);if(n===o)return n}const u=t.slots||{};for(const e of Object.values(u)){if(!e||s(e))continue;const n=f(e,r);if(n===o)return n}}export{o as EXIT,h as extractFirstHeading,p as getHeadingData,f as visit};
1
+ import{isNode as l}from"../../../../../markdoc/helpers/guards/is-node.js";import{isStringNode as f}from"../../../../../markdoc/helpers/guards/is-string-node.js";import{getNodeAttribute as o}from"../../../../../markdoc/helpers/get-node-attribute.js";import{getInnerText as a}from"../../../../../markdoc/helpers/get-inner-text.js";function d(t){const e=o(t,"level");if(e)return{value:a(t.children),id:o(t,"id"),depth:e}}function x(t){const e=o(t,"headlineLevel"),r=o(t,"anchorId"),s=o(t,"headline");if(!(!e||!r||!Array.isArray(s)))return{value:a(s),id:r,depth:e}}function v(t){let e;return u(t,r=>{if(l(r)&&r.type==="heading")return e=d(r)?.value,c}),e}const c=Symbol("Exit visitor");function u(t,e){if(!t)return;const r=e(t);if(r===c)return r;for(const i of t.children){if(!i||f(i))continue;const n=u(i,e);if(n===c)return n}const s=t.slots||{};for(const i of Object.values(s)){if(!i||f(i))continue;const n=u(i,e);if(n===c)return n}}export{c as EXIT,v as extractFirstHeading,d as getHeadingData,x as getNumberedItemHeadingData,u as visit};
@@ -1 +1 @@
1
- import u from"@markdoc/markdoc";import{isTag as b}from"../../../markdoc/helpers/guards/is-tag.js";import{slugger as l}from"../../utils/index.js";import{getMarkdocOptions as f}from"./markdoc/markdoc-options.js";import{visit as h,getHeadingData as k}from"./markdoc/plugins/utils.js";import{renderDiagramsWithVariants as v}from"./markdoc/plugins/render-diagrams.js";import{deepStringify as y}from"./markdoc/utils/deep-stringify.js";import{stringifyTagChildrenObjects as D}from"./utils/stringify-tag-children-objects.js";async function _(d,a,p){const g=await f(p),i=[],t=[],s=y(a.variables?.pageProps),{headers:H,...c}=a.variables||{};l.reset();const o=u.transform(d,{...g,...a,variables:{...c,pageProps:s}});let m=0;h(o,r=>{if(b(r)){switch(r.name){case"Heading":r.attributes.__idx=m,m++;const n=k(r);n&&i.push(n);break;case"Diagram":t.push({tag:r,input:{type:r.attributes.diagramType,source:r.attributes.diagramSource}});break}D(r)}});const e=await v(t.map(r=>r.input));for(let r=0;r<t.length;r++)t[r].tag.attributes.diagramError=e[r].error,t[r].tag.attributes.diagramHtml=e[r].light,t[r].tag.attributes.diagramHtmlDark=e[r].dark;return{headings:i,renderableAst:o,pageProps:s}}export{_ as transformMdAst};
1
+ import b from"@markdoc/markdoc";import{isTag as f}from"../../../markdoc/helpers/guards/is-tag.js";import{slugger as l}from"../../utils/index.js";import{getMarkdocOptions as h}from"./markdoc/markdoc-options.js";import{visit as k,getHeadingData as D,getNumberedItemHeadingData as H}from"./markdoc/plugins/utils.js";import{renderDiagramsWithVariants as v}from"./markdoc/plugins/render-diagrams.js";import{deepStringify as y}from"./markdoc/utils/deep-stringify.js";import{stringifyTagChildrenObjects as T}from"./utils/stringify-tag-children-objects.js";async function _(p,t,g){const u=await h(g),e=[],a=[],s=y(t.variables?.pageProps),{headers:w,...c}=t.variables||{};l.reset();const o=b.transform(p,{...u,...t,variables:{...c,pageProps:s}});let m=0;k(o,r=>{if(f(r)){switch(r.name){case"Heading":r.attributes.__idx=m,m++;const n=D(r);n&&e.push(n);break;case"NumberedItem":const d=H(r);d&&e.push(d);break;case"Diagram":a.push({tag:r,input:{type:r.attributes.diagramType,source:r.attributes.diagramSource}});break}T(r)}});const i=await v(a.map(r=>r.input));for(let r=0;r<a.length;r++)a[r].tag.attributes.diagramError=i[r].error,a[r].tag.attributes.diagramHtml=i[r].light,a[r].tag.attributes.diagramHtmlDark=i[r].dark;return{headings:e,renderableAst:o,pageProps:s}}export{_ as transformMdAst};
@@ -1,5 +1,7 @@
1
1
  import type { ApiFunctionsContext, ApiFunctionsUser } from '@redocly/config';
2
+ import type { Store } from '../../../store.js';
2
3
  export declare function shouldHandleMcpAuth(requiresLogin: boolean, rbacConfig?: Record<string, unknown>): boolean;
4
+ export declare function isMcpContentProtected(store: Store): boolean;
3
5
  export declare function handleMcpAuth(request: Request, context: ApiFunctionsContext): Promise<{
4
6
  isAuthenticated: boolean;
5
7
  isTokenValid?: boolean;
@@ -1 +1 @@
1
- import{extractTokenFromAuthHeader as u}from"../../../plugins/mcp/utils/jwt.js";import{getUserParamsFromCookies as d}from"../../../web-server/auth.js";import{ServerRoutes as l}from"../../../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as i,RBAC_ALL_OTHER_TEAMS as a}from"../../../../constants/common.js";import{withPathPrefix as h}from"@redocly/theme/core/utils";function c(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[i]&&e[i]!=="none"||e[a]&&e[a]!=="none")}function R(e,t){if(!t||Object.keys(t).length===0)return e;const r=t.content;if(r&&Object.keys(r).length>0&&Object.values(r).some(c))return!0;const o=t.teamFoldersBaseRoles;return c(o)?!0:e}async function O(e,t){const r=e.headers.get("Authorization");if(!r)return{isAuthenticated:!1};const o=u(r),s=t?.config?.ssoDirect||{},n=o?await d(s,{authorization:o}):{};return o&&n&&n.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:n.teams||[],email:n.email||"",claims:n,isAuthenticated:!0,idpAccessToken:n.idpAccessToken||void 0,idpId:n.idpId||void 0},accessToken:o}:{isAuthenticated:!1,isTokenValid:!1}}function _(e){return e=e.replace(/^http:\/\//,"https://"),new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer resource_metadata="${e}${l.MCP_OAUTH_PROTECTED_RESOURCE}${h("/mcp")}"`,"Access-Control-Allow-Origin":"*"}})}function g(){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer error="invalid_token", error_description="Invalid or expired token"',"Access-Control-Allow-Origin":"*"}})}export{g as constructInvalidTokenResponse,_ as constructUnauthorizedResponse,O as handleMcpAuth,R as shouldHandleMcpAuth};
1
+ import{extractTokenFromAuthHeader as u}from"../../../plugins/mcp/utils/jwt.js";import{getUserParamsFromCookies as d}from"../../../web-server/auth.js";import{ServerRoutes as l}from"../../../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as i,RBAC_ALL_OTHER_TEAMS as a}from"../../../../constants/common.js";import{withPathPrefix as h}from"@redocly/theme/core/utils";function c(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[i]&&e[i]!=="none"||e[a]&&e[a]!=="none")}function f(e,t){if(!t||Object.keys(t).length===0)return e;const r=t.content;if(r&&Object.keys(r).length>0&&Object.values(r).some(c))return!0;const o=t.teamFoldersBaseRoles;return c(o)?!0:e}function O(e){const t=e.getConfig().access;return f(!!t?.requiresLogin,t?.rbac||{})}async function _(e,t){const r=e.headers.get("Authorization");if(!r)return{isAuthenticated:!1};const o=u(r),s=t?.config?.ssoDirect||{},n=o?await d(s,{authorization:o}):{};return o&&n&&n.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:n.teams||[],email:n.email||"",claims:n,isAuthenticated:!0,idpAccessToken:n.idpAccessToken||void 0,idpId:n.idpId||void 0},accessToken:o}:{isAuthenticated:!1,isTokenValid:!1}}function g(e){return e=e.replace(/^http:\/\//,"https://"),new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer resource_metadata="${e}${l.MCP_OAUTH_PROTECTED_RESOURCE}${h("/mcp")}"`,"Access-Control-Allow-Origin":"*"}})}function x(){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer error="invalid_token", error_description="Invalid or expired token"',"Access-Control-Allow-Origin":"*"}})}export{x as constructInvalidTokenResponse,g as constructUnauthorizedResponse,_ as handleMcpAuth,O as isMcpContentProtected,f as shouldHandleMcpAuth};
@@ -1 +1 @@
1
- import C from"@markdoc/markdoc";import{getPathnameForLocale as A}from"@redocly/theme/core/utils";import{DEFAULT_LOCALE_PLACEHOLDER as g}from"../constants/common.js";import{DEFAULT_TITLE as O}from"./constants/common.js";import{GATED_MARKDOC_TAGS as D}from"./constants/entitlements.js";import{MARKDOC_PARTIALS_DATA_KEY as m,MARKDOC_PARTIALS_DEPS_KEY as T}from"./constants/common.js";import{isObject as M}from"../utils/guards/is-object.js";import{mapObject as k}from"../utils/object/map-object.js";import{getValueDeep as y}from"../utils/object/get-value-deep.js";import{removeTrailingSlash as L}from"../utils/url/remove-trailing-slash.js";import{normalizeRouteSlug as h}from"../utils/path/normalize-route-slug.js";import{isLocalLink as R}from"../utils/path/is-local-link.js";import{reporter as S}from"./tools/notifiers/reporter.js";import{logger as u}from"./tools/notifiers/logger.js";import{sha1 as H}from"./utils/crypto/sha1.js";import{envConfig as _,setEnv as G}from"./config/env-config.js";import{KvService as B}from"./persistence/kv/services/kv-service.js";import{writeSharedData as F}from"./utils/index.js";import{renderComponents as I}from"./ssr/render.js";import{readStaticData as N,writeStaticData as q}from"./utils/static-data.js";import{parseAndResolveMarkdoc as V}from"./plugins/markdown/compiler.js";import{getMarkdocOptions as j}from"./plugins/markdown/markdoc/markdoc-options.js";import{EntitlementsProvider as w}from"./entitlements/entitlements-provider.js";import{isL10nPath as U}from"./fs/utils/is-l10n-path.js";import{resolveGlobMapValue as K}from"./utils/globs.js";import{replaceEnvVariablesDeep as J}from"./utils/envs/replace-env-variables-deep.js";import{findRedirect as x}from"./utils/redirects/find-redirect.js";import{followRedirectChain as W}from"./utils/redirects/follow-redirect-chain.js";import{addWildcardRedirectToTree as $}from"./utils/redirects/add-wildcard-redirect-to-tree.js";import{telemetryTraceStep as z}from"../cli/telemetry/helpers/trace-step.js";const v={routesBySlug:"map",apiRoutes:"object",middleware:"object",routesByFsPath:"map",routesSharedData:"map",globalData:"object",config:"object",ssr:"object",searchFacets:"map",routesPartials:"map",mcpToolHandlers:"map",skills:"map"},E="PLAN_GATES",Y=["OAUTH_CLIENT_ID","OAUTH_CLIENT_SECRET","ORGANIZATION_SLUG","ORGANIZATION_ID","ORG_ID"],Oe="userDefinedApiFunctions";class P{routesBySlug=new Map;replacedEnvVars={};unsetEnvVars=new Set;lifecycleContext;newRoutes=[];#t={};routesByFsPath=new Map;apiRoutes=[];middleware=[];routesSharedData=new Map;sharedDataDeps=new Map;sharedDataMarkdocComponents=new Map;routesDynamicComponents=new Map;routesPartials=new Map;ssr={preBodyTags:[],postBodyTags:[],headTags:[]};searchFacets=new Map;searchEngine;templates=new Map;browserPlugins=new Set;apiRoutesRequestHandlers=new Map;mcpToolHandlers=new Map;skills=new Map;serverPropsGetters=new Map;pagePropsGetters=new Map;listeners=new Map;globalData={};#o=void 0;config={configFilePath:"",redirects:{},wildcardRedirectsTree:{},access:{rbac:{},requiresLogin:!1},directoryPermissions:{},devLogin:!1,ssoDirect:{}};#i;serverMode;serverOutDir;outdir;buildRevision=0;hasSitemap=!1;compilationErrors=[];#n;userCodeReady;#s=Promise.resolve();#c;#l=Promise.resolve();#d;#u=Promise.resolve();#h;#e=!1;#r=0;#f=0;#a=new Map;constructor({outdir:e,contentDir:t,serverMode:s=!1,serverOutDir:a}){this.#i=t,this.outdir=e,this.serverMode=s,this.serverOutDir=a,this.userCodeReady=new Promise(r=>{this.#n=r})}on(e,t){const s=this.listeners.get(e);s?s.add(t):this.listeners.set(e,new Set([t]))}queueEvent=(e,t,...s)=>{this.#a.set(e+String(t),[e,t,...s])};runListeners=(e,t,...s)=>{for(const a of this.listeners.get(e)||new Set)t?a(t,...s):a(...s)};startPluginsRun(){this.clear(),this.#s=new Promise(e=>{this.#c=e})}waitForPluginsLifecycle(){return Promise.all([this.#s,this.#l,this.#u])}waitForPluginsRun(){return this.#s}finishPluginsRun(){this.#c?.();for(const e of this.#a.values())this.runListeners(...e);this.#a.clear()}startEsbuildRun(){this.#l=new Promise(e=>{this.#d=e})}finishEsbuildRun(){this.#d?.()}markRequestHandlersEntryOutdated(){this.#r++,!this.#e&&(this.#e=!0,this.#u=new Promise(e=>{this.#h=e}))}noteRequestHandlersEntryBuildStart(){this.#f=this.#r}markRequestHandlersEntryUpdated(){this.#e&&(this.#r>this.#f||(this.#e=!1,this.#h?.()))}get contentDir(){if(this.serverMode)throw new Error("contentDir should not be used in server mode");return this.#i}markUserCodeReady(){this.#n?.(!0)}async reloadMarkdocOptions(){await z("build.reload_markdoc_options",async()=>{const e=w.instance(),t=await j(this.serverOutDir),s=Object.fromEntries(Object.entries(t.tags).filter(([a])=>D[a]!=null?e.canAccessFeature(D[a]):!0));this.#o={...t,tags:s}})}get markdocOptions(){return{...this.#o,partials:this.getGlobalConfig(m),themeConfig:this.config.markdown}}setGlobalData=e=>{const t=this.globalData,s={...this.globalData,...e};this.globalData=s,JSON.stringify(s)!==JSON.stringify(t)&&this.queueEvent("global-data-updated",void 0,s)};getGlobalData=()=>this.globalData;getKv=async()=>B.getInstance({baseDbDir:this.serverOutDir});parseMarkdoc=async({input:e,context:t,deps:s,resource:a})=>{const{data:{info:r,ast:o},compoundHash:l}=await V(e,this.markdocOptions,{actions:this,context:t},a);for(const i of r.sharedDataDeps||[]){for(const n of s?.routeSlugs||[])this.addRouteSharedData(n,i,i);for(const n of s?.sharedDataIds||[]){const c=this.sharedDataDeps.get(n)||new Set;c.add(i),this.sharedDataDeps.set(n,c)}}for(const i of r.dynamicMarkdocComponents||[]){for(const n of s?.routeSlugs||[]){const c=this.routesDynamicComponents.get(n)||new Set;c.add(i),this.routesDynamicComponents.set(n,c)}for(const n of s?.sharedDataIds||[]){const c=this.sharedDataMarkdocComponents.get(n)||new Set;c.add(i),this.sharedDataMarkdocComponents.set(n,c)}}if(s?.routeSlugs&&r.partials?.length)for(const i of s.routeSlugs){const n=this.routesPartials.get(i)||[];for(const c of r.partials)n.includes(c)||n.push(c);this.routesPartials.set(i,n)}return{info:r,ast:o,compoundHash:l}};async loadOpenApiDefinitions(e){return(await e.cache.load(".","load-oas-docs")).data}async loadAsyncApiDefinitions(e){return(await e.cache.load(".","asyncapi-docs")).data}async loadGraphqlDefinitions(e){return(await e.cache.load(".","load-graphql-docs")).data}setSearchEngine(e){this.searchEngine=e}setSearchFacets=e=>{this.searchFacets=e};setGlobalConfig=e=>{const t=Object.keys(e);for(const o of t)for(const l in this.replacedEnvVars)if(l===o||l.startsWith(`${o}:`)){const i=l.split(":"),{error:n,value:c}=y(e,i);(n||c!==this.replacedEnvVars[l].replaced)&&delete this.replacedEnvVars[l]}const{resolvedObj:s,unsetEnvVars:a,replacedValues:r}=J(e);for(const o of a)this.unsetEnvVars.add(o);Object.assign(this.replacedEnvVars,r),Object.assign(this.config,s)};getConfig=()=>this.config;getGlobalConfig=e=>this.config[e];getSearchFacets=()=>this.searchFacets;addRedirect=(e,t,s={})=>{if(!w.instance().canAccessFeature("redirects")&&e!=="/")return;this.config.redirects||(this.config.redirects={});const o=h(e).toLowerCase();this.config.redirects[o]=t;const{trackOriginalSource:l=!0}=s,i=this.getGlobalConfig("originalRedirectSources");l&&Array.isArray(i)&&!i.includes(o)&&i.push(o),o.endsWith("*")&&$(this.config.wildcardRedirectsTree,o)};getRedirect=e=>{const t=h(e).toLowerCase(),s=x(t,this.config.redirects,this.config.wildcardRedirectsTree);if(!s)return null;if(R(s.to)){const a=h(s.to).toLowerCase();if(!a.endsWith("*")&&W(a,[t],this.config.redirects,this.config.wildcardRedirectsTree).type==="cycle")return null}return{to:s.to,type:s.type}};createSharedData=async(e,t,s)=>{if(s&&this.#t[e]===s)return e;const a=JSON.stringify(t),r=s??H(a);return this.#t[e]===r||(this.#t[e]=r,await F(e,a,this.outdir),this.queueEvent("shared-data-updated",e)),e};addRouteSharedData=(e,t,s)=>{const a=L(e),r=this.routesSharedData.get(a)||{};r[t]=s,this.routesSharedData.set(a,r),u.verbose(`Adding shared data to ${e}, ${t}, ${s}`)};getRouteSharedDataByFsPath=e=>{const t=this.routesByFsPath.get(e);return t?this.routesSharedData.get(t)||{}:{}};getPartialsForRoute=e=>{const t=this.getGlobalConfig(m)||{},s=this.routesPartials.get(e);if(!s||s.length===0)return{};const a=this.getGlobalConfig(T)||{},r=new Set(s),o=Array.from(s);for(let i=0;i<o.length;i++){const n=o[i],c=a[n]?.partials??[];for(const d of c)r.has(d)||(r.add(d),o.push(d))}const l={};for(const i of r)t[i]&&(l[i]=t[i]);return l};addRoute=e=>{const s={...K(e.fsPath,this.config.metadataGlobs),...e.metadata||{}};this.newRoutes.push({...e,metadata:s}),u.verbose("Created route %s",e.slug)};addRouteSharedDataToAllLocales=(e,t,s)=>{const a=[g,...this.lifecycleContext?.fs.localeFolders||[]].map(r=>({code:r,name:r}));for(const r of a){const o=A(e,g,r.code,a);this.addRouteSharedData(o,t,s)}};addApiRoute=e=>{this.apiRoutes.push(e),u.verbose("Created API route %s",e.slug)};addMcpTools=(e,t)=>{for(const s of t)this.mcpToolHandlers.set(s.name,{...s,importPath:e}),u.verbose("Created MCP tool %s",s.name)};getMcpTools=()=>Array.from(this.mcpToolHandlers.values());addSkills=e=>{for(const t of e)this.skills.set(t.slug,t),u.verbose("Discovered skill %s",t.slug)};getSkills=()=>Array.from(this.skills.values());addMiddleware=e=>{this.middleware.push(e),u.verbose("Created middleware %s",e.id)};setResourceResponseHeaders=(e,t)=>{this.config.responseHeaders||(this.config.responseHeaders={});const s=new Set(t.map(o=>o.name.toLowerCase())),r=[...(this.config.responseHeaders[e]??[]).filter(o=>!s.has(o.name.toLowerCase())),...t];this.config.responseHeaders[e]=r};getRouteByFsPath=e=>{const t=this.routesByFsPath.get(e);return t?this.getRouteBySlug(t):void 0};getRouteBySlug=(e,t={})=>{const{followRedirect:s=!0}=t,a=this.getRedirect(e);return s&&a?this.routesBySlug.get(h(a.to)):this.routesBySlug.get(e)};hasRouteOrRedirectBySlug=e=>{if(this.routesBySlug.has(e))return!0;const t=this.getRedirect(e);if(!t)return!1;if(!R(t.to))return!0;const s=h(t.to);return this.routesBySlug.has(s)};getRoutesByTemplateId=e=>this.newRoutes.filter(t=>t.templateId===e);getAllRoutesForLocale=(e=g)=>{const t=Array.from(this.routesBySlug.values()),s=e.toLowerCase();return t.filter(a=>e===g?!U(a.fsPath):a.slug.startsWith(`/${s}`))};getAllRoutes=()=>Array.from(this.routesBySlug.values());getAllApiRoutes=()=>this.apiRoutes;getAllMiddleware=()=>this.middleware;getTemplate=e=>this.templates.get(e);getRequestHandler=e=>this.apiRoutesRequestHandlers.get(e);createTemplate=(e,t)=>(this.templates.set(e,t),e);addBrowserPlugin=e=>{this.browserPlugins.add(e)};createRequestHandler=(e,t)=>(this.apiRoutesRequestHandlers.set(e,t),e);clearRequestHandlersByPrefix=e=>{for(const t of this.apiRoutesRequestHandlers.keys())t.startsWith(e)&&this.apiRoutesRequestHandlers.delete(t)};registerServerPropsGetter=(e,t)=>(this.serverPropsGetters.set(e,t),e);registerPagePropsGetter=(e,t)=>{this.pagePropsGetters.set(e,t)};async writeRouteStaticData(e,t){const s=await this.resolveRouteStaticData(e,t,!1);s&&q(e.slug,s,this.outdir)}async resolveRouteStaticData(e,t,s){if(this.serverMode)return N(e.slug,this.outdir);const a={...this,contentDir:this.contentDir,parseMarkdoc:({input:c,context:d,resource:f})=>this.parseMarkdoc({input:c,context:d,deps:{routeSlugs:[e.slug]},resource:f})},r=await e.getStaticData?.(e,a)||{},o=new Set(this.routesDynamicComponents.get(e.slug)),l=this.routesSharedData.get(e.slug)||{};for(const c of Object.values(l)){const d=this.sharedDataMarkdocComponents.get(c);d&&d.forEach(p=>o.add(p));const f=this.sharedDataDeps.get(c);f&&f.forEach(p=>this.addRouteSharedData(e.slug,p,p))}const i=this.getGlobalConfig("seo"),n=r?.frontmatter||{};return{...r,frontmatter:{...n,seo:{...n?.seo,title:n?.seo?.title||await e.getNavText?.()}},props:{...r.props,dynamicMarkdocComponents:Array.from(o),metadata:{...r?.props?.metadata,...e.metadata},seo:{title:O,...i,...r.props?.seo},compilationErrors:this.compilationErrors},lastModified:s||!e.fsPath?null:await this.lifecycleContext?.fs.getLastModified(e.fsPath)}}addSsrComponents(e,t){if(!e?.length)return;const s=typeof e[0]=="string"?e.join(""):I(e);s&&(t==="head"?this.ssr.headTags.push(s):t==="preBody"?this.ssr.preBodyTags.push(s):this.ssr.postBodyTags.push(s))}clear=()=>{this.routesByFsPath.clear(),this.templates.clear(),this.newRoutes=[],this.routesBySlug.clear(),this.apiRoutes=[],this.middleware=[],this.routesSharedData.clear(),this.sharedDataDeps.clear(),this.sharedDataMarkdocComponents.clear(),this.routesDynamicComponents.clear(),this.routesPartials.clear(),this.skills.clear(),this.config.redirects={},this.config.wildcardRedirectsTree={},this.config.directoryPermissions={},this.ssr={preBodyTags:[],postBodyTags:[],headTags:[]}};async toJson(){const e=[];for(const[s,a]of Object.entries(v))switch(a){case"map":const r=Array.from(this[s].entries());e.push([s,r]);break;case"object":s==="config"&&e.push([s,await this.getConfigWithEnvPlaceholders()]),e.push([s,this[s]]);break;default:throw new Error("Invalid format")}const t=Object.fromEntries(e);return t[E]=_.PLAN_GATES,t}static fromJson(e,t){const s=new P(t);for(const[r,o]of Object.entries(v))switch(o){case"map":s[r]=new Map(e[r]);break;case"object":if(r==="config"){s.setGlobalConfig(e[r]);break}s[r]=e[r];break;default:throw new Error("Invalid format")}s.config[m]=Z(s.config[m]||{});const a=e[E];return a&&G("PLAN_GATES",a),s}async getConfigWithEnvPlaceholders(){const e=JSON.parse(JSON.stringify(this.config));for(const t in this.replacedEnvVars){const{original:s}=this.replacedEnvVars[t],a=t.split(":"),r=a.pop(),{error:o,value:l}=y(e,a);if(o||!M(l)&&!Array.isArray(l)){await S.panicOnBuild(`Failed to replace env var with env name for ${t}`);continue}l[r]=s}return e}async reportUnsetEnvVars(){if(this.unsetEnvVars.size===0)return;const e=Array.from(this.unsetEnvVars).filter(s=>!Y.includes(s));if(e.length===0)return;const t=`Failed to resolve config. The following environment variables are not set: ${e.join(", ")}`;await S.panicOnBuildContentError(t)}}function Z(b){return k(b,e=>C.Ast.fromJSON(JSON.stringify(e)))}export{P as Store,Oe as USER_DEFINED_API_FUNCTIONS_COUNTER_KEY};
1
+ import C from"@markdoc/markdoc";import{getPathnameForLocale as A}from"@redocly/theme/core/utils";import{DEFAULT_LOCALE_PLACEHOLDER as g}from"../constants/common.js";import{DEFAULT_TITLE as T}from"./constants/common.js";import{GATED_MARKDOC_TAGS as y}from"./constants/entitlements.js";import{MARKDOC_PARTIALS_DATA_KEY as m,MARKDOC_PARTIALS_DEPS_KEY as O}from"./constants/common.js";import{isObject as M}from"../utils/guards/is-object.js";import{mapObject as k}from"../utils/object/map-object.js";import{getValueDeep as D}from"../utils/object/get-value-deep.js";import{removeTrailingSlash as L}from"../utils/url/remove-trailing-slash.js";import{normalizeRouteSlug as h}from"../utils/path/normalize-route-slug.js";import{isLocalLink as R}from"../utils/path/is-local-link.js";import{reporter as S}from"./tools/notifiers/reporter.js";import{logger as u}from"./tools/notifiers/logger.js";import{sha1 as H}from"./utils/crypto/sha1.js";import{envConfig as _,setEnv as G}from"./config/env-config.js";import{KvService as B}from"./persistence/kv/services/kv-service.js";import{SandboxContext as F}from"./utils/sandbox-context.js";import{writeSharedData as I}from"./utils/index.js";import{renderComponents as N}from"./ssr/render.js";import{readStaticData as q,writeStaticData as V}from"./utils/static-data.js";import{parseAndResolveMarkdoc as j}from"./plugins/markdown/compiler.js";import{getMarkdocOptions as U}from"./plugins/markdown/markdoc/markdoc-options.js";import{EntitlementsProvider as w}from"./entitlements/entitlements-provider.js";import{isL10nPath as K}from"./fs/utils/is-l10n-path.js";import{resolveGlobMapValue as x}from"./utils/globs.js";import{replaceEnvVariablesDeep as J}from"./utils/envs/replace-env-variables-deep.js";import{findRedirect as W}from"./utils/redirects/find-redirect.js";import{followRedirectChain as $}from"./utils/redirects/follow-redirect-chain.js";import{addWildcardRedirectToTree as z}from"./utils/redirects/add-wildcard-redirect-to-tree.js";import{telemetryTraceStep as Y}from"../cli/telemetry/helpers/trace-step.js";const v={routesBySlug:"map",apiRoutes:"object",middleware:"object",routesByFsPath:"map",routesSharedData:"map",globalData:"object",config:"object",ssr:"object",searchFacets:"map",routesPartials:"map",mcpToolHandlers:"map",skills:"map"},E="PLAN_GATES",Z=["OAUTH_CLIENT_ID","OAUTH_CLIENT_SECRET","ORGANIZATION_SLUG","ORGANIZATION_ID","ORG_ID"],Me="userDefinedApiFunctions";class P{routesBySlug=new Map;replacedEnvVars={};unsetEnvVars=new Set;lifecycleContext;newRoutes=[];#t={};routesByFsPath=new Map;apiRoutes=[];middleware=[];routesSharedData=new Map;sharedDataDeps=new Map;sharedDataMarkdocComponents=new Map;routesDynamicComponents=new Map;routesPartials=new Map;ssr={preBodyTags:[],postBodyTags:[],headTags:[]};searchFacets=new Map;searchEngine;templates=new Map;browserPlugins=new Set;apiRoutesRequestHandlers=new Map;mcpToolHandlers=new Map;skills=new Map;serverPropsGetters=new Map;pagePropsGetters=new Map;listeners=new Map;globalData={};#o=void 0;config={configFilePath:"",redirects:{},wildcardRedirectsTree:{},access:{rbac:{},requiresLogin:!1},directoryPermissions:{},devLogin:!1,ssoDirect:{}};#i;serverMode;serverOutDir;outdir;buildRevision=0;hasSitemap=!1;compilationErrors=[];#n;userCodeReady;#s=Promise.resolve();#c;#l=Promise.resolve();#d;#u=Promise.resolve();#h;#e=!1;#r=0;#f=0;#a=new Map;constructor({outdir:e,contentDir:t,serverMode:s=!1,serverOutDir:a}){this.#i=t,this.outdir=e,this.serverMode=s,this.serverOutDir=a,F.register({serverOutDir:a,readRoots:[e,process.cwd()]}),this.userCodeReady=new Promise(r=>{this.#n=r})}on(e,t){const s=this.listeners.get(e);s?s.add(t):this.listeners.set(e,new Set([t]))}queueEvent=(e,t,...s)=>{this.#a.set(e+String(t),[e,t,...s])};runListeners=(e,t,...s)=>{for(const a of this.listeners.get(e)||new Set)t?a(t,...s):a(...s)};startPluginsRun(){this.clear(),this.#s=new Promise(e=>{this.#c=e})}waitForPluginsLifecycle(){return Promise.all([this.#s,this.#l,this.#u])}waitForPluginsRun(){return this.#s}finishPluginsRun(){this.#c?.();for(const e of this.#a.values())this.runListeners(...e);this.#a.clear()}startEsbuildRun(){this.#l=new Promise(e=>{this.#d=e})}finishEsbuildRun(){this.#d?.()}markRequestHandlersEntryOutdated(){this.#r++,!this.#e&&(this.#e=!0,this.#u=new Promise(e=>{this.#h=e}))}noteRequestHandlersEntryBuildStart(){this.#f=this.#r}markRequestHandlersEntryUpdated(){this.#e&&(this.#r>this.#f||(this.#e=!1,this.#h?.()))}get contentDir(){if(this.serverMode)throw new Error("contentDir should not be used in server mode");return this.#i}markUserCodeReady(){this.#n?.(!0)}async reloadMarkdocOptions(){await Y("build.reload_markdoc_options",async()=>{const e=w.instance(),t=await U(this.serverOutDir),s=Object.fromEntries(Object.entries(t.tags).filter(([a])=>y[a]!=null?e.canAccessFeature(y[a]):!0));this.#o={...t,tags:s}})}get markdocOptions(){return{...this.#o,partials:this.getGlobalConfig(m),themeConfig:this.config.markdown}}setGlobalData=e=>{const t=this.globalData,s={...this.globalData,...e};this.globalData=s,JSON.stringify(s)!==JSON.stringify(t)&&this.queueEvent("global-data-updated",void 0,s)};getGlobalData=()=>this.globalData;getKv=async()=>B.getInstance({baseDbDir:this.serverOutDir});parseMarkdoc=async({input:e,context:t,deps:s,resource:a})=>{const{data:{info:r,ast:o},compoundHash:l}=await j(e,this.markdocOptions,{actions:this,context:t},a);for(const i of r.sharedDataDeps||[]){for(const n of s?.routeSlugs||[])this.addRouteSharedData(n,i,i);for(const n of s?.sharedDataIds||[]){const c=this.sharedDataDeps.get(n)||new Set;c.add(i),this.sharedDataDeps.set(n,c)}}for(const i of r.dynamicMarkdocComponents||[]){for(const n of s?.routeSlugs||[]){const c=this.routesDynamicComponents.get(n)||new Set;c.add(i),this.routesDynamicComponents.set(n,c)}for(const n of s?.sharedDataIds||[]){const c=this.sharedDataMarkdocComponents.get(n)||new Set;c.add(i),this.sharedDataMarkdocComponents.set(n,c)}}if(s?.routeSlugs&&r.partials?.length)for(const i of s.routeSlugs){const n=this.routesPartials.get(i)||[];for(const c of r.partials)n.includes(c)||n.push(c);this.routesPartials.set(i,n)}return{info:r,ast:o,compoundHash:l}};async loadOpenApiDefinitions(e){return(await e.cache.load(".","load-oas-docs")).data}async loadAsyncApiDefinitions(e){return(await e.cache.load(".","asyncapi-docs")).data}async loadGraphqlDefinitions(e){return(await e.cache.load(".","load-graphql-docs")).data}setSearchEngine(e){this.searchEngine=e}setSearchFacets=e=>{this.searchFacets=e};setGlobalConfig=e=>{const t=Object.keys(e);for(const o of t)for(const l in this.replacedEnvVars)if(l===o||l.startsWith(`${o}:`)){const i=l.split(":"),{error:n,value:c}=D(e,i);(n||c!==this.replacedEnvVars[l].replaced)&&delete this.replacedEnvVars[l]}const{resolvedObj:s,unsetEnvVars:a,replacedValues:r}=J(e);for(const o of a)this.unsetEnvVars.add(o);Object.assign(this.replacedEnvVars,r),Object.assign(this.config,s)};getConfig=()=>this.config;getGlobalConfig=e=>this.config[e];getSearchFacets=()=>this.searchFacets;addRedirect=(e,t,s={})=>{if(!w.instance().canAccessFeature("redirects")&&e!=="/")return;this.config.redirects||(this.config.redirects={});const o=h(e).toLowerCase();this.config.redirects[o]=t;const{trackOriginalSource:l=!0}=s,i=this.getGlobalConfig("originalRedirectSources");l&&Array.isArray(i)&&!i.includes(o)&&i.push(o),o.endsWith("*")&&z(this.config.wildcardRedirectsTree,o)};getRedirect=e=>{const t=h(e).toLowerCase(),s=W(t,this.config.redirects,this.config.wildcardRedirectsTree);if(!s)return null;if(R(s.to)){const a=h(s.to).toLowerCase();if(!a.endsWith("*")&&$(a,[t],this.config.redirects,this.config.wildcardRedirectsTree).type==="cycle")return null}return{to:s.to,type:s.type}};createSharedData=async(e,t,s)=>{if(s&&this.#t[e]===s)return e;const a=JSON.stringify(t),r=s??H(a);return this.#t[e]===r||(this.#t[e]=r,await I(e,a,this.outdir),this.queueEvent("shared-data-updated",e)),e};addRouteSharedData=(e,t,s)=>{const a=L(e),r=this.routesSharedData.get(a)||{};r[t]=s,this.routesSharedData.set(a,r),u.verbose(`Adding shared data to ${e}, ${t}, ${s}`)};getRouteSharedDataByFsPath=e=>{const t=this.routesByFsPath.get(e);return t?this.routesSharedData.get(t)||{}:{}};getPartialsForRoute=e=>{const t=this.getGlobalConfig(m)||{},s=this.routesPartials.get(e);if(!s||s.length===0)return{};const a=this.getGlobalConfig(O)||{},r=new Set(s),o=Array.from(s);for(let i=0;i<o.length;i++){const n=o[i],c=a[n]?.partials??[];for(const d of c)r.has(d)||(r.add(d),o.push(d))}const l={};for(const i of r)t[i]&&(l[i]=t[i]);return l};addRoute=e=>{const s={...x(e.fsPath,this.config.metadataGlobs),...e.metadata||{}};this.newRoutes.push({...e,metadata:s}),u.verbose("Created route %s",e.slug)};addRouteSharedDataToAllLocales=(e,t,s)=>{const a=[g,...this.lifecycleContext?.fs.localeFolders||[]].map(r=>({code:r,name:r}));for(const r of a){const o=A(e,g,r.code,a);this.addRouteSharedData(o,t,s)}};addApiRoute=e=>{this.apiRoutes.push(e),u.verbose("Created API route %s",e.slug)};addMcpTools=(e,t)=>{for(const s of t)this.mcpToolHandlers.set(s.name,{...s,importPath:e}),u.verbose("Created MCP tool %s",s.name)};getMcpTools=()=>Array.from(this.mcpToolHandlers.values());addSkills=e=>{for(const t of e)this.skills.set(t.slug,t),u.verbose("Discovered skill %s",t.slug)};getSkills=()=>Array.from(this.skills.values());addMiddleware=e=>{this.middleware.push(e),u.verbose("Created middleware %s",e.id)};setResourceResponseHeaders=(e,t)=>{this.config.responseHeaders||(this.config.responseHeaders={});const s=new Set(t.map(o=>o.name.toLowerCase())),r=[...(this.config.responseHeaders[e]??[]).filter(o=>!s.has(o.name.toLowerCase())),...t];this.config.responseHeaders[e]=r};getRouteByFsPath=e=>{const t=this.routesByFsPath.get(e);return t?this.getRouteBySlug(t):void 0};getRouteBySlug=(e,t={})=>{const{followRedirect:s=!0}=t,a=this.getRedirect(e);return s&&a?this.routesBySlug.get(h(a.to)):this.routesBySlug.get(e)};hasRouteOrRedirectBySlug=e=>{if(this.routesBySlug.has(e))return!0;const t=this.getRedirect(e);if(!t)return!1;if(!R(t.to))return!0;const s=h(t.to);return this.routesBySlug.has(s)};getRoutesByTemplateId=e=>this.newRoutes.filter(t=>t.templateId===e);getAllRoutesForLocale=(e=g)=>{const t=Array.from(this.routesBySlug.values()),s=e.toLowerCase();return t.filter(a=>e===g?!K(a.fsPath):a.slug.startsWith(`/${s}`))};getAllRoutes=()=>Array.from(this.routesBySlug.values());getAllApiRoutes=()=>this.apiRoutes;getAllMiddleware=()=>this.middleware;getTemplate=e=>this.templates.get(e);getRequestHandler=e=>this.apiRoutesRequestHandlers.get(e);createTemplate=(e,t)=>(this.templates.set(e,t),e);addBrowserPlugin=e=>{this.browserPlugins.add(e)};createRequestHandler=(e,t)=>(this.apiRoutesRequestHandlers.set(e,t),e);clearRequestHandlersByPrefix=e=>{for(const t of this.apiRoutesRequestHandlers.keys())t.startsWith(e)&&this.apiRoutesRequestHandlers.delete(t)};registerServerPropsGetter=(e,t)=>(this.serverPropsGetters.set(e,t),e);registerPagePropsGetter=(e,t)=>{this.pagePropsGetters.set(e,t)};async writeRouteStaticData(e,t){const s=await this.resolveRouteStaticData(e,t,!1);s&&V(e.slug,s,this.outdir)}async resolveRouteStaticData(e,t,s){if(this.serverMode)return q(e.slug,this.outdir);const a={...this,contentDir:this.contentDir,parseMarkdoc:({input:c,context:d,resource:f})=>this.parseMarkdoc({input:c,context:d,deps:{routeSlugs:[e.slug]},resource:f})},r=await e.getStaticData?.(e,a)||{},o=new Set(this.routesDynamicComponents.get(e.slug)),l=this.routesSharedData.get(e.slug)||{};for(const c of Object.values(l)){const d=this.sharedDataMarkdocComponents.get(c);d&&d.forEach(p=>o.add(p));const f=this.sharedDataDeps.get(c);f&&f.forEach(p=>this.addRouteSharedData(e.slug,p,p))}const i=this.getGlobalConfig("seo"),n=r?.frontmatter||{};return{...r,frontmatter:{...n,seo:{...n?.seo,title:n?.seo?.title||await e.getNavText?.()}},props:{...r.props,dynamicMarkdocComponents:Array.from(o),metadata:{...r?.props?.metadata,...e.metadata},seo:{title:T,...i,...r.props?.seo},compilationErrors:this.compilationErrors},lastModified:s||!e.fsPath?null:await this.lifecycleContext?.fs.getLastModified(e.fsPath)}}addSsrComponents(e,t){if(!e?.length)return;const s=typeof e[0]=="string"?e.join(""):N(e);s&&(t==="head"?this.ssr.headTags.push(s):t==="preBody"?this.ssr.preBodyTags.push(s):this.ssr.postBodyTags.push(s))}clear=()=>{this.routesByFsPath.clear(),this.templates.clear(),this.newRoutes=[],this.routesBySlug.clear(),this.apiRoutes=[],this.middleware=[],this.routesSharedData.clear(),this.sharedDataDeps.clear(),this.sharedDataMarkdocComponents.clear(),this.routesDynamicComponents.clear(),this.routesPartials.clear(),this.skills.clear(),this.config.redirects={},this.config.wildcardRedirectsTree={},this.config.directoryPermissions={},this.ssr={preBodyTags:[],postBodyTags:[],headTags:[]}};async toJson(){const e=[];for(const[s,a]of Object.entries(v))switch(a){case"map":const r=Array.from(this[s].entries());e.push([s,r]);break;case"object":s==="config"&&e.push([s,await this.getConfigWithEnvPlaceholders()]),e.push([s,this[s]]);break;default:throw new Error("Invalid format")}const t=Object.fromEntries(e);return t[E]=_.PLAN_GATES,t}static fromJson(e,t){const s=new P(t);for(const[r,o]of Object.entries(v))switch(o){case"map":s[r]=new Map(e[r]);break;case"object":if(r==="config"){s.setGlobalConfig(e[r]);break}s[r]=e[r];break;default:throw new Error("Invalid format")}s.config[m]=Q(s.config[m]||{});const a=e[E];return a&&G("PLAN_GATES",a),s}async getConfigWithEnvPlaceholders(){const e=JSON.parse(JSON.stringify(this.config));for(const t in this.replacedEnvVars){const{original:s}=this.replacedEnvVars[t],a=t.split(":"),r=a.pop(),{error:o,value:l}=D(e,a);if(o||!M(l)&&!Array.isArray(l)){await S.panicOnBuild(`Failed to replace env var with env name for ${t}`);continue}l[r]=s}return e}async reportUnsetEnvVars(){if(this.unsetEnvVars.size===0)return;const e=Array.from(this.unsetEnvVars).filter(s=>!Z.includes(s));if(e.length===0)return;const t=`Failed to resolve config. The following environment variables are not set: ${e.join(", ")}`;await S.panicOnBuildContentError(t)}}function Q(b){return k(b,e=>C.Ast.fromJSON(JSON.stringify(e)))}export{P as Store,Me as USER_DEFINED_API_FUNCTIONS_COUNTER_KEY};
@@ -0,0 +1,18 @@
1
+ export declare class SandboxContext {
2
+ #private;
3
+ readonly serverOutDir: string;
4
+ static register({ serverOutDir, readRoots }: {
5
+ serverOutDir: string;
6
+ readRoots?: string[];
7
+ }): void;
8
+ static resolve(fallbackServerOutDir: string): SandboxContext;
9
+ private constructor();
10
+ get tmpDir(): string;
11
+ get execArgv(): string[];
12
+ get env(): {
13
+ TMPDIR: string;
14
+ TMP: string;
15
+ TEMP: string;
16
+ };
17
+ }
18
+ //# sourceMappingURL=sandbox-context.d.ts.map
@@ -0,0 +1 @@
1
+ import{existsSync as a}from"node:fs";import{dirname as l,join as m}from"node:path";class s{serverOutDir;static#e;static register({serverOutDir:e,readRoots:t}){s.#e=new s(e,t)}static resolve(e){return s.#e??new s(e)}#t;constructor(e,t=[]){this.serverOutDir=e,this.#t=s.#n(e,t)}get tmpDir(){return`${this.serverOutDir}/.data/tmp`}get execArgv(){return["--permission",...this.#t.map(e=>`--allow-fs-read=${e}/*`),`--allow-fs-write=${this.serverOutDir}/.data/*`,"--allow-addons"]}get env(){return{TMPDIR:this.tmpDir,TMP:this.tmpDir,TEMP:this.tmpDir}}static#r(e){if(!e)return e;const r=e.indexOf("/node_modules/");return r!==-1?e.slice(0,r):s.#s(e)??s.#i(e)??e}static#s(e){let t=e;for(;;){if(a(m(t,"node_modules",".pnpm")))return t;const r=l(t);if(r===t)return;t=r}}static#i(e){let t=e;for(;;){if(a(m(t,"node_modules")))return t;const r=l(t);if(r===t)return;t=r}}static#n(e,t){const r=[s.#r(e),...t].filter(Boolean).map(i=>i.replace(/\/+$/,"")).filter(Boolean),n=[...new Set(r)];return n.filter(i=>!n.some(o=>o!==i&&i.startsWith(`${o}/`)))}}export{s as SandboxContext};
@@ -1,4 +1,5 @@
1
1
  import type { Handler } from 'hono';
2
+ import type { Store } from '../../../store.js';
2
3
  export type McpContextPayload = {
3
4
  isMcpFlow: boolean;
4
5
  originalRedirectUri: string | null;
@@ -12,14 +13,14 @@ export type McpContextPayload = {
12
13
  export declare function verifyPkce(codeChallenge: string, codeChallengeMethod: string | undefined, codeVerifier: string): boolean;
13
14
  export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
14
15
  export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
15
- export declare function mcpOAuthProtectedResourceHandler(): Handler;
16
- export declare function mcpOAuthAuthorizationServerHandler(): Handler;
16
+ export declare function mcpOAuthProtectedResourceHandler(store: Store): Handler;
17
+ export declare function mcpOAuthAuthorizationServerHandler(store: Store): Handler;
17
18
  /**
18
19
  * This code is not used in the MCP server, but it is required to satisfy the OAuth 2.0 Dynamic Client Registration Protocol.
19
20
  * Also it is simulating the client registration process.
20
21
  */
21
- export declare function mcpDynamicClientRegistrationHandler(): Handler;
22
- export declare function mcpAuthorizationHandler(): Handler;
22
+ export declare function mcpDynamicClientRegistrationHandler(store: Store): Handler;
23
+ export declare function mcpAuthorizationHandler(store: Store): Handler;
23
24
  export declare function mcpTokenPortalHandler(): Handler;
24
25
  export declare function mcpCallbackHandler(): Handler;
25
26
  //# sourceMappingURL=mcp-oauth.d.ts.map
@@ -1 +1 @@
1
- import{getCookie as C}from"hono/cookie";import{createHash as U,timingSafeEqual as R}from"node:crypto";import{ulid as v}from"ulid";import{AUTH_URL as f,JWT_SECRET_KEY as M}from"../../../constants/common.js";import{ServerRoutes as _}from"../../../../constants/api.js";import{AuthCookieNames as T}from"../../../../constants/common.js";import{withPathPrefix as u}from"@redocly/theme/core/utils";import{telemetry as m}from"../../../telemetry/index.js";import{envConfig as A}from"../../../config/env-config.js";import{createMcpAuthorizationCode as O,verifyMcpAuthorizationCode as D,createMcpSessionResource as h}from"../../auth.js";import*as g from"../../jwt/jwt.js";import{AlgorithmTypes as S}from"../../jwt/types.js";import{getRequestOrigin as y}from"../../utils/get-request-origin.js";import{fetchClientMetadataDocument as L,isClientMetadataDocumentUrl as k,isRedirectUriRegistered as H}from"./mcp-client-metadata.js";const o=(e,r,n=200,a)=>e.json(r,n,{"Content-Type":"application/json",...a??{}}),I=new Set(["S256"]),q="https://claude.ai/oauth/claude-code-client-metadata";function z(e,r,n){if(!I.has(r||""))return!1;const a=U("sha256").update(n).digest("base64url"),t=Buffer.from(a),s=Buffer.from(e);return t.length!==s.length?!1:R(t,s)}async function $(e){const r=Math.floor(Date.now()/1e3);return g.sign({type:"mcp_context",...e,iat:r,exp:r+600},M,S.HS256)}async function N(e){await g.verify(e,M,S.HS256);const{payload:r}=g.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function x(){return async e=>{if(e.req.method!=="GET")return o(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=y(e);return o(e,{resource:`${r}${u("/mcp")}`,authorization_servers:[r,f].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${r}${_.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function ee(){return async e=>{const r=y(e);return o(e,{issuer:f||"",authorization_endpoint:`${r}${u(_.MCP_AUTHORIZATION)}`,token_endpoint:`${r}${u(_.MCP_TOKEN_PORTAL)}`,jwks_uri:`${f||""}/.well-known/jwks.json`,scopes_supported:["openid","profile","email","offline_access"],registration_endpoint:`${r}${u(_.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0})}}function re(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{return o(e,{client_id:A.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(r){return o(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function te(){return async e=>{const r=new URL(e.req.url),{searchParams:n}=r,a=n.get("redirect_uri"),t=n.get("client_id"),s=n.get("code_challenge"),i=n.get("code_challenge_method"),d=v();if(s&&!I.has(i||""))return o(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...I].join(", ")}`},400);if(k(t)&&!s)return o(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(k(t)&&a)try{const c=await L(t),l=t===q;if(!H(a,c.redirect_uris,{ignorePort:l}))return o(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(c){const l=c instanceof Error?c.message:String(c);return o(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${l}`},400)}m.sendMcpAuthorizationStartedMessage([{...h(d),redirect_uri:a||null}]);const p=y(e),w={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:t,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:s,mcpCodeChallengeMethod:s?i:null,timestamp:Date.now()};try{const c=await $(w),l=new URL(u(_.IDP_LOGIN),p);return l.searchParams.set("redirectTo",`${_.MCP_CALLBACK}/${c}`),l.searchParams.set("idpId","oidc"),e.redirect(l.toString())}catch(c){const l=c instanceof Error?c.message:String(c),P=c instanceof Error?c.stack:String(c);m.sendMcpAuthorizationFailedMessage([{...h(d),error:l,error_details:P}]);const E=new URL(u(`${f}/oauth2/auth`));return E.search=n.toString(),e.redirect(E.toString())}}}function oe(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),n=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0,s=r.get("code_verifier")||void 0;if(n!=="authorization_code"||!a)return o(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await D(a);if(t&&t!==i.redirect_uri)return o(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(i.code_challenge){if(!s)return o(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!z(i.code_challenge,i.code_challenge_method,s))return o(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(A.OAUTH_CLIENT_ID&&i.client_id&&!k(i.client_id)&&i.client_id!==A.OAUTH_CLIENT_ID)return o(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=i.id_token;if(typeof d!="string"||d.length===0)return o(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let p=d;if(i.idp_access_token){const{payload:c,header:{kid:l}}=g.decode(d);p=await g.sign({...c,idp_access_token:i.idp_access_token},M,S.HS256,l)}return o(e,{access_token:p,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:d},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return o(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const n=r instanceof Error?r.message:String(r);return o(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}function ne(){return async e=>{const r=new URL(e.req.url);let n=r.searchParams.get("context");if(!n&&r.pathname.startsWith(u(`${_.MCP_CALLBACK}/`))){const t=r.pathname.split("/");n=t[t.length-1]}if(!n)return m.sendMcpAuthorizationFailedMessage([{...h(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let a=null;try{const t=await N(n);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=C(e,T.IDP_ID_TOKEN)||C(e,T.AUTHORIZATION),i=C(e,T.IDP_ACCESS_TOKEN);if(!s)return m.sendMcpAuthorizationFailedMessage([{...h(a),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await O({idToken:s,idpAccessToken:i||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),p=new URL(t.originalRedirectUri);return p.searchParams.set("code",d),t.mcpState&&p.searchParams.set("state",t.mcpState),m.sendMcpAuthorizationCompletedMessage([{...h(a),redirect_uri:t.originalRedirectUri||null}]),e.redirect(p.toString())}catch(t){const s=t instanceof Error?t.message:String(t),i=t instanceof Error?t.stack:String(t);return m.sendMcpAuthorizationFailedMessage([{...h(a),error:s,error_details:i}]),e.text(`Invalid MCP callback: ${s}`,400)}}}export{$ as createMcpContextToken,te as mcpAuthorizationHandler,ne as mcpCallbackHandler,re as mcpDynamicClientRegistrationHandler,ee as mcpOAuthAuthorizationServerHandler,x as mcpOAuthProtectedResourceHandler,oe as mcpTokenPortalHandler,N as verifyAndParseMcpContextToken,z as verifyPkce};
1
+ import{getCookie as S}from"hono/cookie";import{createHash as D,timingSafeEqual as L}from"node:crypto";import{ulid as H}from"ulid";import{isMcpContentProtected as C}from"../../../plugins/mcp/auth/auth-handlers.js";import{isRedoclySso as q,isOidcProviderConfig as z}from"../../auth.js";import{AUTH_URL as M,JWT_SECRET_KEY as A}from"../../../constants/common.js";import{ServerRoutes as u}from"../../../../constants/api.js";import{AuthCookieNames as k}from"../../../../constants/common.js";import{withPathPrefix as _}from"@redocly/theme/core/utils";import{telemetry as m}from"../../../telemetry/index.js";import{envConfig as I}from"../../../config/env-config.js";import{createMcpAuthorizationCode as $,verifyMcpAuthorizationCode as N,createMcpSessionResource as f}from"../../auth.js";import*as h from"../../jwt/jwt.js";import{AlgorithmTypes as w}from"../../jwt/types.js";import{getRequestOrigin as E}from"../../utils/get-request-origin.js";import{fetchClientMetadataDocument as b,isClientMetadataDocumentUrl as P,isRedirectUriRegistered as j}from"./mcp-client-metadata.js";const o=(r,e,n=200,a)=>r.json(e,n,{"Content-Type":"application/json",...a??{}}),U=new Set(["S256"]),B="https://claude.ai/oauth/claude-code-client-metadata";function F(r,e,n){if(!U.has(e||""))return!1;const a=D("sha256").update(n).digest("base64url"),t=Buffer.from(a),s=Buffer.from(r);return t.length!==s.length?!1:L(t,s)}async function K(r){const e=Math.floor(Date.now()/1e3);return h.sign({type:"mcp_context",...r,iat:e,exp:e+600},A,w.HS256)}async function G(r){await h.verify(r,A,w.HS256);const{payload:e}=h.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function se(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);if(e.req.method!=="GET")return o(e,{error:"Method not allowed"},405,{Allow:"GET"});const n=E(e);return o(e,{resource:`${n}${_("/mcp")}`,authorization_servers:[n,M].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${n}${u.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function ce(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);const n=E(e);return o(e,{issuer:M||"",authorization_endpoint:`${n}${_(u.MCP_AUTHORIZATION)}`,token_endpoint:`${n}${_(u.MCP_TOKEN_PORTAL)}`,jwks_uri:`${M||""}/.well-known/jwks.json`,scopes_supported:["openid","profile","email","offline_access"],registration_endpoint:`${n}${_(u.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0})}}function de(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{return o(e,{client_id:I.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(n){return o(e,{error:"invalid_request",error_description:n?.message||"Unable to register client"},500)}}}function le(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);const n=new URL(e.req.url),{searchParams:a}=n,t=a.get("redirect_uri"),s=a.get("client_id"),i=a.get("code_challenge"),d=a.get("code_challenge_method"),l=H();if(i&&!U.has(d||""))return o(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...U].join(", ")}`},400);if(P(s)&&!i)return o(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(P(s)&&t)try{const c=await b(s),p=s===B;if(!j(t,c.redirect_uris,{ignorePort:p}))return o(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(c){const p=c instanceof Error?c.message:String(c);return o(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${p}`},400)}m.sendMcpAuthorizationStartedMessage([{...f(l),redirect_uri:t||null}]);const v=E(e),T={isMcpFlow:!0,originalRedirectUri:t,mcpClientId:s,mcpState:a.get("state"),mcpSessionId:l,mcpCodeChallenge:i,mcpCodeChallengeMethod:i?d:null,timestamp:Date.now()};try{const c=await K(T),p=new URL(_(u.IDP_LOGIN),v);p.searchParams.set("redirectTo",`${u.MCP_CALLBACK}/${c}`);const g=r.getConfig().ssoDirect||{},R=Object.entries(g).find(([,O])=>z(O)&&q(O))?.[0]??Object.keys(g)[0];return R?(p.searchParams.set("idpId",R),e.redirect(p.toString())):o(e,{error:"not_found"},404)}catch(c){const p=c instanceof Error?c.message:String(c),g=c instanceof Error?c.stack:String(c);m.sendMcpAuthorizationFailedMessage([{...f(l),error:p,error_details:g}]);const y=new URL(_(`${M}/oauth2/auth`));return y.search=a.toString(),e.redirect(y.toString())}}}function pe(){return async r=>{if(r.req.method!=="POST")return o(r,{error:"Method not allowed"},405);try{const e=await r.req.formData(),n=e.get("grant_type"),a=e.get("code"),t=e.get("redirect_uri")||void 0,s=e.get("code_verifier")||void 0;if(n!=="authorization_code"||!a)return o(r,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await N(a);if(t&&t!==i.redirect_uri)return o(r,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(i.code_challenge){if(!s)return o(r,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!F(i.code_challenge,i.code_challenge_method,s))return o(r,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(I.OAUTH_CLIENT_ID&&i.client_id&&!P(i.client_id)&&i.client_id!==I.OAUTH_CLIENT_ID)return o(r,{error:"invalid_client",error_description:"Client mismatch"},400);const d=i.id_token;if(typeof d!="string"||d.length===0)return o(r,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let l=d;if(i.idp_access_token){const{payload:T,header:{kid:c}}=h.decode(d);l=await h.sign({...T,idp_access_token:i.idp_access_token},A,w.HS256,c)}return o(r,{access_token:l,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:d},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return o(r,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(e){const n=e instanceof Error?e.message:String(e);return o(r,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}function ue(){return async r=>{const e=new URL(r.req.url);let n=e.searchParams.get("context");if(!n&&e.pathname.startsWith(_(`${u.MCP_CALLBACK}/`))){const t=e.pathname.split("/");n=t[t.length-1]}if(!n)return m.sendMcpAuthorizationFailedMessage([{...f(null),error:"Missing context parameter",error_details:null}]),r.text("Missing context parameter",400);let a=null;try{const t=await G(n);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=S(r,k.IDP_ID_TOKEN)||S(r,k.AUTHORIZATION),i=S(r,k.IDP_ACCESS_TOKEN);if(!s)return m.sendMcpAuthorizationFailedMessage([{...f(a),error:"Authentication required",error_details:null}]),r.text("Authentication required",401);const d=await $({idToken:s,idpAccessToken:i||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(t.originalRedirectUri);return l.searchParams.set("code",d),t.mcpState&&l.searchParams.set("state",t.mcpState),m.sendMcpAuthorizationCompletedMessage([{...f(a),redirect_uri:t.originalRedirectUri||null}]),r.redirect(l.toString())}catch(t){const s=t instanceof Error?t.message:String(t),i=t instanceof Error?t.stack:String(t);return m.sendMcpAuthorizationFailedMessage([{...f(a),error:s,error_details:i}]),r.text(`Invalid MCP callback: ${s}`,400)}}}export{K as createMcpContextToken,le as mcpAuthorizationHandler,ue as mcpCallbackHandler,de as mcpDynamicClientRegistrationHandler,ce as mcpOAuthAuthorizationServerHandler,se as mcpOAuthProtectedResourceHandler,pe as mcpTokenPortalHandler,G as verifyAndParseMcpContextToken,F as verifyPkce};
@@ -1 +1 @@
1
- import{corsMiddleware as p}from"../../middleware/corsMiddleware.js";import{ServerRoutes as n}from"../../../../constants/api.js";import{EntitlementsProvider as r}from"../../../entitlements/entitlements-provider.js";import{withPathPrefix as t}from"@redocly/theme/core/utils";import{AGENT_CARD_SLUG as C,A2A_SLUG as a,MCP_SERVER_CARD_SLUG as l}from"../../../../constants/common.js";import{mcpOAuthProtectedResourceHandler as A,mcpOAuthAuthorizationServerHandler as d,mcpDynamicClientRegistrationHandler as s,mcpAuthorizationHandler as _,mcpTokenPortalHandler as R,mcpCallbackHandler as m}from"./mcp-oauth.js";import{mcpServerCardHandler as E}from"./mcp-server-card.js";import{agentCardHandler as P}from"./agent-card.js";import{a2aHandler as T}from"./a2a.js";function N(e,i){const o=i.getConfig().mcp,c=r.instance().canAccessFeature("mcp");!o?.hide&&!o?.docs?.hide&&c&&(e.get(t(l),E(i)),e.get(t(C),P(i)),e.use(t(a),p({allowMethods:["POST"],exposeHeaders:["Link"]})),e.post(t(a),T(i)),e.get(`${n.MCP_OAUTH_PROTECTED_RESOURCE}${t("/mcp")}`,A()),e.get(n.MCP_OAUTH_AUTHORIZATION_SERVER,d()),e.post(t(n.MCP_DYNAMIC_CLIENT_REGISTRATION),s()),e.get(t(n.MCP_AUTHORIZATION),_()),e.post(t(n.MCP_TOKEN_PORTAL),R()),e.get(t(n.MCP_CALLBACK),m()),e.get(t(`${n.MCP_CALLBACK}/*`),m()))}export{N as installMcpRoutes};
1
+ import{corsMiddleware as p}from"../../middleware/corsMiddleware.js";import{ServerRoutes as i}from"../../../../constants/api.js";import{EntitlementsProvider as C}from"../../../entitlements/entitlements-provider.js";import{withPathPrefix as n}from"@redocly/theme/core/utils";import{AGENT_CARD_SLUG as l,A2A_SLUG as m,MCP_SERVER_CARD_SLUG as A}from"../../../../constants/common.js";import{mcpOAuthProtectedResourceHandler as d,mcpOAuthAuthorizationServerHandler as r,mcpDynamicClientRegistrationHandler as _,mcpAuthorizationHandler as s,mcpTokenPortalHandler as R,mcpCallbackHandler as o}from"./mcp-oauth.js";import{mcpServerCardHandler as E}from"./mcp-server-card.js";import{agentCardHandler as P}from"./agent-card.js";import{a2aHandler as T}from"./a2a.js";function N(e,t){const a=t.getConfig().mcp,c=C.instance().canAccessFeature("mcp");!a?.hide&&!a?.docs?.hide&&c&&(e.get(n(A),E(t)),e.get(n(l),P(t)),e.use(n(m),p({allowMethods:["POST"],exposeHeaders:["Link"]})),e.post(n(m),T(t)),e.get(`${i.MCP_OAUTH_PROTECTED_RESOURCE}${n("/mcp")}`,d(t)),e.get(i.MCP_OAUTH_AUTHORIZATION_SERVER,r(t)),e.post(n(i.MCP_DYNAMIC_CLIENT_REGISTRATION),_(t)),e.get(n(i.MCP_AUTHORIZATION),s(t)),e.post(n(i.MCP_TOKEN_PORTAL),R()),e.get(n(i.MCP_CALLBACK),o()),e.get(n(`${i.MCP_CALLBACK}/*`),o()))}export{N as installMcpRoutes};
@@ -1 +1 @@
1
- import{getAllowedEnvs as r}from"../utils/envs/get-api-route-allowed-env-variables.js";import{WorkerPool as o}from"./worker-pool.js";const p="executeApiRoute",e=3,i=new o({workerScript:"./api-routes-worker",minWorkers:1,maxWorkers:e,workerType:"process",forkOpts:{stdio:"inherit"},resolveForkEnv:r,lazy:!0});export{p as API_ROUTES_WORKER_KEY,i as apiRoutesWorkers};
1
+ import{getAllowedEnvs as r}from"../utils/envs/get-api-route-allowed-env-variables.js";import{WorkerPool as o}from"./worker-pool.js";const p="executeApiRoute",e=3,i=new o({workerScript:"./api-routes-worker",minWorkers:1,maxWorkers:e,workerType:"process",forkOpts:{stdio:"inherit"},resolveForkEnv:r,lazy:!0,sandbox:!0});export{p as API_ROUTES_WORKER_KEY,i as apiRoutesWorkers};
@@ -1 +1 @@
1
- import{getAllowedEnvs as o}from"../utils/envs/get-api-route-allowed-env-variables.js";import{WorkerPool as r}from"./worker-pool.js";const p="executeMcpTool",c="isMcpToolAvailable",e=5,l=new r({workerScript:"./mcp-tool-worker",minWorkers:1,maxWorkers:e,workerType:"process",forkOpts:{stdio:"inherit"},resolveForkEnv:o,lazy:!0});export{c as MCP_TOOL_IS_AVAILABLE_KEY,p as MCP_TOOL_WORKER_KEY,l as mcpToolWorkers};
1
+ import{getAllowedEnvs as o}from"../utils/envs/get-api-route-allowed-env-variables.js";import{WorkerPool as r}from"./worker-pool.js";const p="executeMcpTool",c="isMcpToolAvailable",e=5,l=new r({workerScript:"./mcp-tool-worker",minWorkers:1,maxWorkers:e,workerType:"process",forkOpts:{stdio:"inherit"},resolveForkEnv:o,lazy:!0,sandbox:!0});export{c as MCP_TOOL_IS_AVAILABLE_KEY,p as MCP_TOOL_WORKER_KEY,l as mcpToolWorkers};
@@ -3,6 +3,7 @@ import type { WorkerTypeMapping, WorkerParams, WorkerResponse } from './types.js
3
3
  export type WorkerPoolConfig = {
4
4
  workerScript: string;
5
5
  lazy: boolean;
6
+ sandbox?: boolean;
6
7
  resolveForkEnv?: () => Record<string, string | undefined>;
7
8
  } & WorkerPoolOptions;
8
9
  type RegisteredWorkers = keyof WorkerTypeMapping;
@@ -1 +1 @@
1
- import n from"workerpool";import{basename as a,dirname as s,resolve as l}from"node:path";import{fileURLToPath as h}from"node:url";import{envConfig as m}from"../config/env-config.js";class v{#t=null;#r;constructor(o){this.#r=o,this.#r.lazy||(this.#t=this.#o())}#o(){const{workerScript:o,lazy:t,resolveForkEnv:r,...e}=this.#r;let i=this.#e(o);return r&&e.forkOpts&&(e.forkOpts={...e.forkOpts,env:r()}),n.pool(i,{...e})}#e(o){let t=o;if(!t.endsWith(".js")&&!t.endsWith(".mjs")&&(t=`${t}.${m.isDevelopMode?"js":"mjs"}`),!t.startsWith("."))return t;const r=s(h(import.meta.url)),e=a(r)==="chunks"?s(r):r;return l(e,t)}#i(){return this.#t||(this.#t=this.#o()),this.#t}async exec(o,t,r){let i=this.#i().exec(o,t);return r?.timeout&&(i=i.timeout(r.timeout)),await i}async terminate(){this.#t&&(await this.#t.terminate(),this.#t=null)}}export{v as WorkerPool};
1
+ import l from"workerpool";import{mkdirSync as m}from"node:fs";import{basename as u,dirname as c,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";import{envConfig as n}from"../config/env-config.js";import{SandboxContext as v}from"../utils/sandbox-context.js";const h=(()=>{const o=c(p(import.meta.url));return u(o)==="chunks"?c(o):o})();class D{#t=null;#r;constructor(r){this.#r=r,this.#r.lazy||(this.#t=this.#e())}#e(){const{workerScript:r,lazy:t,sandbox:i,resolveForkEnv:s,...e}=this.#r,a=this.#s(r);return s&&e.forkOpts&&(e.forkOpts={...e.forkOpts,env:s()}),this.#o(i)?l.pool(a,{...e,forkOpts:this.#i(e.forkOpts)}):l.pool(a,{...e})}#o(r){return!!r&&n.isReunite&&n.REDOCLY_DISABLE_API_FUNCTION_SANDBOX!==!0}#i(r){const t=v.resolve(h);try{m(t.tmpDir,{recursive:!0})}catch{}return{...r,execArgv:[...t.execArgv,...r?.execArgv??[]],env:{...r?.env,...t.env}}}#s(r){let t=r;return!t.endsWith(".js")&&!t.endsWith(".mjs")&&(t=`${t}.${n.isDevelopMode?"js":"mjs"}`),t.startsWith(".")?f(h,t):t}#n(){return this.#t||(this.#t=this.#e()),this.#t}async exec(r,t,i){let e=this.#n().exec(r,t);return i?.timeout&&(e=e.timeout(i.timeout)),await e}async terminate(){this.#t&&(await this.#t.terminate(),this.#t=null)}}export{D as WorkerPool};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/realm",
3
- "version": "0.136.0-next.1",
3
+ "version": "0.136.0-next.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -93,14 +93,14 @@
93
93
  "xpath": "0.0.34",
94
94
  "yaml-ast-parser": "0.0.43",
95
95
  "zod": "^3.25.76",
96
- "@redocly/asyncapi-docs": "1.13.0-next.1",
96
+ "@redocly/asyncapi-docs": "1.13.0-next.2",
97
97
  "@redocly/config": "0.52.0",
98
- "@redocly/graphql-docs": "1.13.0-next.1",
99
- "@redocly/openapi-docs": "3.24.0-next.1",
98
+ "@redocly/graphql-docs": "1.13.0-next.2",
99
+ "@redocly/openapi-docs": "3.24.0-next.2",
100
100
  "@redocly/portal-legacy-ui": "0.18.0",
101
- "@redocly/portal-plugin-mock-server": "0.21.0-next.1",
101
+ "@redocly/portal-plugin-mock-server": "0.21.0-next.2",
102
102
  "@redocly/realm-asyncapi-sdk": "0.14.0-next.0",
103
- "@redocly/theme": "0.68.0-next.0"
103
+ "@redocly/theme": "0.68.0-next.1"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "react": "^19.2.4",