From e97b87136717ed3063a0b7bebe5506c3d8d6f633 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 12 Dec 2024 14:35:37 +0100 Subject: [PATCH] catalog-backend: moved createEntityArrayJsonStream to response utils Signed-off-by: Patrik Oldsberg --- .../src/service/createRouter.ts | 2 +- .../response/createEntityArrayJsonStream.ts | 79 +++++++++++++++++++ .../src/service/response/index.ts | 1 + plugins/catalog-backend/src/service/util.ts | 64 +-------------- 4 files changed, 82 insertions(+), 64 deletions(-) create mode 100644 plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index f89ae745ce..6ea9ea1c13 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -41,7 +41,6 @@ import { parseEntityFacetParams } from './request/parseEntityFacetParams'; import { parseEntityOrderParams } from './request/parseEntityOrderParams'; import { LocationService, RefreshService } from './types'; import { - createEntityArrayJsonStream, disallowReadonlyMode, encodeCursor, expandLegacyCompoundRelationsInEntity, @@ -61,6 +60,7 @@ import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; import { AuthorizedValidationService } from './AuthorizedValidationService'; import { DeferredPromise, createDeferred } from '@backstage/types'; import { + createEntityArrayJsonStream, processEntitiesResponseItems, writeEntitiesResponse, writeSingleEntityResponse, diff --git a/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts b/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts new file mode 100644 index 0000000000..4762afd2d9 --- /dev/null +++ b/plugins/catalog-backend/src/service/response/createEntityArrayJsonStream.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EntitiesResponseItems } from '../../catalog/types'; +import { Response } from 'express'; + +export interface EntityArrayJsonStream { + send(entities: EntitiesResponseItems): boolean; + complete(): void; + close(): void; +} + +// Helps stream EntitiesResponseItems[] as a JSON response stream to avoid performance issues +export function createEntityArrayJsonStream( + res: Response, +): EntityArrayJsonStream { + // Imitate the httpRouter behavior of pretty-printing in development + const prettyPrint = process.env.NODE_ENV === 'development'; + let firstSend = true; + let completed = false; + + return { + send(response) { + if (firstSend) { + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.status(200); + res.flushHeaders(); + } + + if (response.type === 'raw') { + let needsDrain = false; + for (const item of response.entities) { + const prefix = firstSend ? '[' : ','; + firstSend = false; + needsDrain ||= !res.write(prefix + item, 'utf8'); + } + return !needsDrain; + } + + let data: string; + if (prettyPrint) { + data = JSON.stringify(response.entities, null, 2); + data = firstSend ? data.slice(0, -2) : `,\n${data.slice(2, -2)}`; + } else { + data = JSON.stringify(response.entities); + data = firstSend ? data.slice(0, -1) : `,${data.slice(1, -1)}`; + } + + firstSend = false; + return res.write(data, 'utf8'); + }, + complete() { + if (firstSend) { + res.json([]); + } else { + res.end(prettyPrint ? '\n]' : ']', 'utf8'); + } + completed = true; + }, + close() { + if (!completed) { + res.end(); + } + }, + }; +} diff --git a/plugins/catalog-backend/src/service/response/index.ts b/plugins/catalog-backend/src/service/response/index.ts index 73c82c2aee..2e01071dbd 100644 --- a/plugins/catalog-backend/src/service/response/index.ts +++ b/plugins/catalog-backend/src/service/response/index.ts @@ -20,3 +20,4 @@ export { entitiesResponseToObjects, } from './process'; export { writeSingleEntityResponse, writeEntitiesResponse } from './write'; +export { createEntityArrayJsonStream } from './createEntityArrayJsonStream'; diff --git a/plugins/catalog-backend/src/service/util.ts b/plugins/catalog-backend/src/service/util.ts index 7ff7c8fcb3..9847cb7225 100644 --- a/plugins/catalog-backend/src/service/util.ts +++ b/plugins/catalog-backend/src/service/util.ts @@ -15,12 +15,11 @@ */ import { InputError, NotAllowedError } from '@backstage/errors'; -import { Request, Response } from 'express'; +import { Request } from 'express'; import lodash from 'lodash'; import { z } from 'zod'; import { Cursor, - EntitiesResponseItems, QueryEntitiesCursorRequest, QueryEntitiesInitialRequest, QueryEntitiesRequest, @@ -167,64 +166,3 @@ export function expandLegacyCompoundRelationsInEntity(entity: Entity): Entity { } return entity; } - -export interface EntityArrayJsonStream { - send(entities: EntitiesResponseItems): boolean; - complete(): void; - close(): void; -} - -// Helps stream EntitiesResponseItems[] as a JSON response stream to avoid performance issues -export function createEntityArrayJsonStream( - res: Response, -): EntityArrayJsonStream { - // Imitate the httpRouter behavior of pretty-printing in development - const prettyPrint = process.env.NODE_ENV === 'development'; - let firstSend = true; - let completed = false; - - return { - send(response) { - if (firstSend) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.status(200); - res.flushHeaders(); - } - - if (response.type === 'raw') { - let needsDrain = false; - for (const item of response.entities) { - const prefix = firstSend ? '[' : ','; - firstSend = false; - needsDrain ||= !res.write(prefix + item, 'utf8'); - } - return !needsDrain; - } - - let data: string; - if (prettyPrint) { - data = JSON.stringify(response.entities, null, 2); - data = firstSend ? data.slice(0, -2) : `,\n${data.slice(2, -2)}`; - } else { - data = JSON.stringify(response.entities); - data = firstSend ? data.slice(0, -1) : `,${data.slice(1, -1)}`; - } - - firstSend = false; - return res.write(data, 'utf8'); - }, - complete() { - if (firstSend) { - res.json([]); - } else { - res.end(prettyPrint ? '\n]' : ']', 'utf8'); - } - completed = true; - }, - close() { - if (!completed) { - res.end(); - } - }, - }; -}