backend-dev-utils: add DevDataStore
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: blam <ben@blam.sh> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
## API Report File for "@backstage/backend-dev-utils"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
// @public
|
||||
export class DevDataStore {
|
||||
static get(): DevDataStore | undefined;
|
||||
load<T>(key: string): Promise<{
|
||||
loaded: boolean;
|
||||
data: T;
|
||||
}>;
|
||||
save<T>(
|
||||
key: string,
|
||||
data: T,
|
||||
): Promise<{
|
||||
saved: boolean;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Serializable, spawn } from 'child_process';
|
||||
import { DevDataStore } from './DevDataStore';
|
||||
import { BackstageIpcClient } from './ipcClient';
|
||||
|
||||
function applyIpcTransform(value: Serializable): Promise<Serializable> {
|
||||
const child = spawn(
|
||||
'node',
|
||||
[
|
||||
'--eval',
|
||||
`
|
||||
const listener = msg => {
|
||||
process.send(msg);
|
||||
process.removeListener('message', listener);
|
||||
}
|
||||
process.addListener('message', listener);
|
||||
`,
|
||||
],
|
||||
{
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
serialization: 'advanced',
|
||||
},
|
||||
);
|
||||
|
||||
return new Promise(resolve => {
|
||||
child.addListener('message', resolve);
|
||||
child.send(value);
|
||||
});
|
||||
}
|
||||
|
||||
class MockIpcClient implements Pick<BackstageIpcClient, 'request'> {
|
||||
#data = new Map<string, Serializable>();
|
||||
|
||||
async request(method: string, body: any): Promise<any> {
|
||||
if (method === 'DevDataStore.save') {
|
||||
this.#data.set(body.key, body.data);
|
||||
return { saved: true };
|
||||
} else if (method === 'DevDataStore.load') {
|
||||
if (!this.#data.has(body.key)) {
|
||||
return { loaded: false, data: undefined };
|
||||
}
|
||||
const data = this.#data.get(body.key)!;
|
||||
return { loaded: true, data: await applyIpcTransform(data) };
|
||||
}
|
||||
throw new Error('Unknown message');
|
||||
}
|
||||
}
|
||||
|
||||
describe('DevDataStore', () => {
|
||||
it('should save and load data', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(store.save('test', { foo: 'bar' })).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: { foo: 'bar' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should save and load buffers', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(
|
||||
store.save('test', Buffer.from('abc', 'ascii')),
|
||||
).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: Buffer.from('abc', 'ascii'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should save and load array buffers', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(
|
||||
store.save('test', new Uint8Array(Buffer.from('abc', 'ascii'))),
|
||||
).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: new Uint8Array(Buffer.from('abc', 'ascii')),
|
||||
});
|
||||
});
|
||||
|
||||
it('should save and load buffers nested in objects', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(
|
||||
store.save('test', {
|
||||
x: Buffer.from('x', 'ascii'),
|
||||
y: [Buffer.from('y', 'ascii')],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: { x: Buffer.from('x', 'ascii'), y: [Buffer.from('y', 'ascii')] },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2023 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 { BackstageIpcClient, ipcClient } from './ipcClient';
|
||||
|
||||
interface SaveRequest {
|
||||
key: string;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
interface SaveResponse {
|
||||
saved: boolean;
|
||||
}
|
||||
|
||||
interface LoadRequest {
|
||||
key: string;
|
||||
}
|
||||
|
||||
interface LoadResponse {
|
||||
loaded: boolean;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A data store that can be used to store temporary data during development.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DevDataStore {
|
||||
static #instance?: DevDataStore;
|
||||
|
||||
/**
|
||||
* Tries to acquire a DevDataStore instance. This will only succeed when the backend
|
||||
* process is being run through the `@backstage/cli` in development mode.
|
||||
*
|
||||
* @returns A DevDataStore instance, or undefined if not available.
|
||||
*/
|
||||
static get(): DevDataStore | undefined {
|
||||
if (ipcClient) {
|
||||
if (!this.#instance) {
|
||||
this.#instance = new DevDataStore(ipcClient);
|
||||
}
|
||||
return this.#instance;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static forTest(client: Pick<BackstageIpcClient, 'request'>): DevDataStore {
|
||||
return new DevDataStore(client as BackstageIpcClient);
|
||||
}
|
||||
|
||||
#client: BackstageIpcClient;
|
||||
|
||||
private constructor(client: BackstageIpcClient) {
|
||||
this.#client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to the data store.
|
||||
*
|
||||
* @param key - The key used to identify the data.
|
||||
* @param data - The data to save. The data will be serialized using advanced IPC serialization.
|
||||
* @returns A promise that resolves to a result object that indicates whether the data was saved.
|
||||
*/
|
||||
async save<T>(key: string, data: T): Promise<{ saved: boolean }> {
|
||||
return this.#client.request<SaveRequest, SaveResponse>(
|
||||
'DevDataStore.save',
|
||||
{ key, data },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data from the data store.
|
||||
*
|
||||
* @param key - The key used to identify the data.
|
||||
* @returns A promise that resolves to a result object that indicates whether the data was loaded, as well as the data.
|
||||
*/
|
||||
async load<T>(key: string): Promise<{ loaded: boolean; data: T }> {
|
||||
const result = await this.#client.request<LoadRequest, LoadResponse>(
|
||||
'DevDataStore.load',
|
||||
{ key },
|
||||
);
|
||||
return result as { loaded: boolean; data: T };
|
||||
}
|
||||
}
|
||||
@@ -19,3 +19,5 @@
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { DevDataStore } from './DevDataStore';
|
||||
|
||||
Reference in New Issue
Block a user