catalog-backend: moved createEntityArrayJsonStream to response utils

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-12-12 14:35:37 +01:00
parent 31e2ca8d18
commit e97b871367
4 changed files with 82 additions and 64 deletions
@@ -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,
@@ -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();
}
},
};
}
@@ -20,3 +20,4 @@ export {
entitiesResponseToObjects,
} from './process';
export { writeSingleEntityResponse, writeEntitiesResponse } from './write';
export { createEntityArrayJsonStream } from './createEntityArrayJsonStream';
+1 -63
View File
@@ -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();
}
},
};
}