Add multiget to usersettings to batch get()s in one call and cache the result for a short period
Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings-backend': minor
|
||||
'@backstage/plugin-user-settings-common': minor
|
||||
'@backstage/plugin-user-settings': minor
|
||||
---
|
||||
|
||||
User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance
|
||||
@@ -55,6 +55,7 @@
|
||||
"@backstage/plugin-signals-node": "workspace:^",
|
||||
"@backstage/plugin-user-settings-common": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"already": "^2.2.1",
|
||||
"express": "^4.22.0",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^3.0.0"
|
||||
|
||||
@@ -14,12 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { InputError, serializeError } from '@backstage/errors';
|
||||
import { map } from 'already';
|
||||
import express, { Request } from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { UserSettingsStore } from '../database/UserSettingsStore';
|
||||
import { SignalsService } from '@backstage/plugin-signals-node';
|
||||
import { UserSettingsSignal } from '@backstage/plugin-user-settings-common';
|
||||
import {
|
||||
MultiUserSetting,
|
||||
UserSettingsSignal,
|
||||
parseDataLoaderKey,
|
||||
} from '@backstage/plugin-user-settings-common';
|
||||
import { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
|
||||
export async function createRouter(options: {
|
||||
@@ -40,6 +45,59 @@ export async function createRouter(options: {
|
||||
return credentials.principal.userEntityRef;
|
||||
};
|
||||
|
||||
// get multiple values
|
||||
router.get('/multi', async (req, res) => {
|
||||
const userEntityRef = await getUserEntityRef(req);
|
||||
|
||||
const bucketsAndKeys: ReturnType<typeof parseDataLoaderKey>[] = [];
|
||||
|
||||
const items = req.query.items;
|
||||
if (typeof items === 'string') {
|
||||
bucketsAndKeys.push(parseDataLoaderKey(items));
|
||||
} else if (Array.isArray(items)) {
|
||||
bucketsAndKeys.push(
|
||||
...items.map(item => {
|
||||
if (typeof item !== 'string') {
|
||||
throw new InputError(
|
||||
'Expected query param "items" to be an array of strings',
|
||||
);
|
||||
}
|
||||
return parseDataLoaderKey(item);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
throw new InputError('Expected query param "items" to be an array');
|
||||
}
|
||||
|
||||
const userSettings = await map(
|
||||
bucketsAndKeys,
|
||||
{ concurrency: 10 },
|
||||
async ({ bucket, key }): Promise<MultiUserSetting> => {
|
||||
try {
|
||||
const setting = await options.userSettingsStore.get({
|
||||
userEntityRef,
|
||||
bucket,
|
||||
key,
|
||||
});
|
||||
return setting;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
const serialized = serializeError(e);
|
||||
return { bucket, key, error: serialized };
|
||||
}
|
||||
|
||||
return {
|
||||
bucket,
|
||||
key,
|
||||
error: { name: 'Error', message: 'Unknown error' },
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
res.json(userSettings);
|
||||
});
|
||||
|
||||
// get a single value
|
||||
router.get('/buckets/:bucket/keys/:key(*)', async (req, res) => {
|
||||
const userEntityRef = await getUserEntityRef(req);
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/types": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,40 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import type { JsonValue } from '@backstage/types';
|
||||
import type { SerializedError } from '@backstage/errors';
|
||||
|
||||
// @public (undocumented)
|
||||
export function isMultiUserSettingError(
|
||||
setting: MultiUserSetting,
|
||||
): setting is MultiUserSettingError;
|
||||
|
||||
// @public
|
||||
export type MultiUserSetting = MultiUserSettingError | MultiUserSettingSuccess;
|
||||
|
||||
// @public
|
||||
export type MultiUserSettingError = {
|
||||
bucket: string;
|
||||
key: string;
|
||||
error: SerializedError;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type MultiUserSettingSuccess = {
|
||||
bucket: string;
|
||||
key: string;
|
||||
value: JsonValue;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function parseDataLoaderKey(bucketAndKey: string): {
|
||||
bucket: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function stringifyDataLoaderKey(bucket: string, key: string): string;
|
||||
|
||||
// @public (undocumented)
|
||||
export type UserSettingsSignal = {
|
||||
type: 'key-changed' | 'key-deleted';
|
||||
|
||||
@@ -15,3 +15,6 @@
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export { isMultiUserSettingError } from './types';
|
||||
|
||||
export { stringifyDataLoaderKey, parseDataLoaderKey } from './keys';
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export function stringifyDataLoaderKey(bucket: string, key: string) {
|
||||
return `${encodeURIComponent(bucket)}/${encodeURIComponent(key)}`;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export function parseDataLoaderKey(bucketAndKey: string) {
|
||||
const [bucket, key] = bucketAndKey.split('/');
|
||||
return { bucket: decodeURIComponent(bucket), key: decodeURIComponent(key) };
|
||||
}
|
||||
@@ -14,8 +14,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { SerializedError } from '@backstage/errors';
|
||||
import type { JsonValue } from '@backstage/types';
|
||||
|
||||
/** @public */
|
||||
export type UserSettingsSignal = {
|
||||
type: 'key-changed' | 'key-deleted';
|
||||
key: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A failed fetch of a user setting in a bucket
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type MultiUserSettingError = {
|
||||
bucket: string;
|
||||
key: string;
|
||||
error: SerializedError;
|
||||
};
|
||||
|
||||
/**
|
||||
* A successful value of a user setting in a bucket
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type MultiUserSettingSuccess = {
|
||||
bucket: string;
|
||||
key: string;
|
||||
value: JsonValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* A single setting in a bucket, or an error, used result from the /multi endpoint
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type MultiUserSetting = MultiUserSettingError | MultiUserSettingSuccess;
|
||||
|
||||
/** @public */
|
||||
export function isMultiUserSettingError(
|
||||
setting: MultiUserSetting,
|
||||
): setting is MultiUserSettingError {
|
||||
return 'error' in setting;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"dataloader": "^2.0.0",
|
||||
"react-use": "^17.2.4",
|
||||
"zen-observable": "^0.10.0"
|
||||
},
|
||||
@@ -82,6 +83,7 @@
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"already": "^2.2.1",
|
||||
"msw": "^1.0.0",
|
||||
"react": "^18.0.2",
|
||||
"react-dom": "^18.0.2",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Map<K, V> with TTL (time-to-live) support, for the Dataloader of user settings.
|
||||
*/
|
||||
export class CacheMap<K, V> extends Map<K, V> {
|
||||
#ttlMs: number;
|
||||
#timestamps: Map<K, number> = new Map();
|
||||
|
||||
constructor(ttlMs: number) {
|
||||
super();
|
||||
this.#ttlMs = ttlMs;
|
||||
}
|
||||
|
||||
set(key: K, value: V) {
|
||||
const result = super.set(key, value);
|
||||
this.#timestamps.set(key, Date.now());
|
||||
return result;
|
||||
}
|
||||
|
||||
get(key: K) {
|
||||
if (!this.has(key)) {
|
||||
return undefined;
|
||||
}
|
||||
const timestamp = this.#timestamps.get(key)!;
|
||||
if (Date.now() - timestamp > this.#ttlMs) {
|
||||
this.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
delete(key: K) {
|
||||
this.#timestamps.delete(key);
|
||||
return super.delete(key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#timestamps.clear();
|
||||
return super.clear();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@ import {
|
||||
mockApis,
|
||||
registerMswTestHooks,
|
||||
} from '@backstage/test-utils';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { parseDataLoaderKey } from '@backstage/plugin-user-settings-common';
|
||||
import { defer } from 'already';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { UserSettingsStorage } from './UserSettingsStorage';
|
||||
@@ -41,14 +44,12 @@ describe('Persistent Storage API', () => {
|
||||
const mockIdentityApi = mockApis.identity({ token: 'a-token' });
|
||||
const mockIdentityApiFallback = mockApis.identity();
|
||||
|
||||
const createPersistentStorage = (
|
||||
args?: Partial<{
|
||||
fetchApi: FetchApi;
|
||||
discoveryApi: DiscoveryApi;
|
||||
errorApi: ErrorApi;
|
||||
namespace?: string;
|
||||
}>,
|
||||
): StorageApi => {
|
||||
const createPersistentStorage = (args: {
|
||||
fetchApi?: FetchApi;
|
||||
discoveryApi?: DiscoveryApi;
|
||||
errorApi?: ErrorApi;
|
||||
namespace: string;
|
||||
}): StorageApi => {
|
||||
return UserSettingsStorage.create({
|
||||
errorApi: mockErrorApi,
|
||||
fetchApi: new MockFetchApi(),
|
||||
@@ -58,14 +59,12 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const createPersistentStorageFallback = (
|
||||
args?: Partial<{
|
||||
fetchApi: FetchApi;
|
||||
discoveryApi: DiscoveryApi;
|
||||
errorApi: ErrorApi;
|
||||
namespace?: string;
|
||||
}>,
|
||||
): StorageApi => {
|
||||
const createPersistentStorageFallback = (args: {
|
||||
fetchApi?: FetchApi;
|
||||
discoveryApi?: DiscoveryApi;
|
||||
errorApi?: ErrorApi;
|
||||
namespace: string;
|
||||
}): StorageApi => {
|
||||
return UserSettingsStorage.create({
|
||||
errorApi: mockErrorApi,
|
||||
fetchApi: new MockFetchApi(),
|
||||
@@ -75,21 +74,17 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
// Wait for server callbacks to settle before clearing the handlers.
|
||||
// DataLoader delay is 10ms, so this should be plenty.
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
jest.clearAllMocks();
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('should return undefined for values which are unset', async () => {
|
||||
const storage = createPersistentStorage();
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
`${mockBaseUrl}/buckets/:bucket/keys/:key`,
|
||||
async (_req, res, ctx) => {
|
||||
return res(ctx.json({ value: 'a' }));
|
||||
},
|
||||
),
|
||||
);
|
||||
const storage = createPersistentStorage({ namespace: 'undefined' });
|
||||
|
||||
expect(storage.snapshot('myfakekey').value).toBeUndefined();
|
||||
expect(storage.snapshot('myfakekey')).toEqual({
|
||||
@@ -100,7 +95,7 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
|
||||
it('should allow setting of a simple data structure', async () => {
|
||||
const storage = createPersistentStorage();
|
||||
const storage = createPersistentStorage({ namespace: 'simple' });
|
||||
const dummyValue = 'a';
|
||||
|
||||
server.use(
|
||||
@@ -121,7 +116,7 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
|
||||
it('should allow setting of a complex data structure', async () => {
|
||||
const storage = createPersistentStorage();
|
||||
const storage = createPersistentStorage({ namespace: 'complex' });
|
||||
const dummyValue = {
|
||||
some: 'nice data',
|
||||
with: { nested: 'values', nice: true },
|
||||
@@ -144,7 +139,9 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
|
||||
it('should fallback set when user not logged in', async () => {
|
||||
const storage = createPersistentStorageFallback();
|
||||
const storage = createPersistentStorageFallback({
|
||||
namespace: 'not-logged-in',
|
||||
});
|
||||
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
const dummyValue = 'my-value';
|
||||
@@ -171,12 +168,14 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
|
||||
it('should subscribe to key changes when setting a new value', async () => {
|
||||
const storage = createPersistentStorage();
|
||||
const storage = createPersistentStorage({ namespace: 'key-change-set' });
|
||||
|
||||
const wrongKeyNextHandler = jest.fn();
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
const mockData = { hello: 'im a great new value' };
|
||||
|
||||
const serverCall = defer(undefined);
|
||||
|
||||
server.use(
|
||||
rest.put(
|
||||
`${mockBaseUrl}/buckets/:bucket/keys/:key`,
|
||||
@@ -188,6 +187,10 @@ describe('Persistent Storage API', () => {
|
||||
return res(ctx.json(data));
|
||||
},
|
||||
),
|
||||
rest.get(`${mockBaseUrl}/multi`, async (_req, res, ctx) => {
|
||||
serverCall.resolve();
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
);
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
@@ -212,14 +215,18 @@ describe('Persistent Storage API', () => {
|
||||
presence: 'present',
|
||||
value: mockData,
|
||||
});
|
||||
|
||||
await serverCall.promise;
|
||||
});
|
||||
|
||||
it('should subscribe to key changes when deleting a value', async () => {
|
||||
const storage = createPersistentStorage();
|
||||
const storage = createPersistentStorage({ namespace: 'key-change-delete' });
|
||||
|
||||
const wrongKeyNextHandler = jest.fn();
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
|
||||
const serverCall = defer(undefined);
|
||||
|
||||
server.use(
|
||||
rest.delete(
|
||||
`${mockBaseUrl}/buckets/:bucket/keys/:key`,
|
||||
@@ -227,6 +234,10 @@ describe('Persistent Storage API', () => {
|
||||
return res(ctx.status(204));
|
||||
},
|
||||
),
|
||||
rest.get(`${mockBaseUrl}/multi`, async (_req, res, ctx) => {
|
||||
serverCall.resolve();
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
);
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
@@ -251,10 +262,12 @@ describe('Persistent Storage API', () => {
|
||||
presence: 'absent',
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
await serverCall.promise;
|
||||
});
|
||||
|
||||
it('should not clash with other namespaces when creating buckets', async () => {
|
||||
const rootStorage = createPersistentStorage();
|
||||
const rootStorage = createPersistentStorage({ namespace: 'clash' });
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
|
||||
server.use(
|
||||
@@ -264,23 +277,22 @@ describe('Persistent Storage API', () => {
|
||||
const { bucket, key } = req.params;
|
||||
const { value } = await req.json();
|
||||
|
||||
expect(bucket).toEqual('default.profile.something.deep');
|
||||
expect(bucket).toEqual('clash.profile.something.deep');
|
||||
expect(key).toEqual('test2');
|
||||
|
||||
return res(ctx.json({ value }));
|
||||
},
|
||||
),
|
||||
rest.get(
|
||||
`${mockBaseUrl}/buckets/:bucket/keys/:key`,
|
||||
async (req, res, ctx) => {
|
||||
const { bucket, key } = req.params;
|
||||
rest.get(`${mockBaseUrl}/multi`, async (req, res, ctx) => {
|
||||
const { bucket, key } = parseDataLoaderKey(
|
||||
req.url.searchParams.get('items') as string,
|
||||
);
|
||||
|
||||
expect(bucket).toEqual('default.profile/something');
|
||||
expect(key).toEqual('deep/test2');
|
||||
expect(bucket).toEqual('clash.profile/something');
|
||||
expect(key).toEqual('deep/test2');
|
||||
|
||||
return res(ctx.status(404));
|
||||
},
|
||||
),
|
||||
return res(ctx.status(404));
|
||||
}),
|
||||
);
|
||||
|
||||
// when getting key test2 it will translate to default.profile.something.deep/test2
|
||||
@@ -321,15 +333,14 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
`${mockBaseUrl}/buckets/:bucket/keys/:key`,
|
||||
async (req, res, ctx) => {
|
||||
const { bucket, key } = req.params;
|
||||
expect(bucket).toEqual('Test.Mock.Thing');
|
||||
expect(key).toEqual('key');
|
||||
return res(ctx.text('{ invalid: json string }'));
|
||||
},
|
||||
),
|
||||
rest.get(`${mockBaseUrl}/multi`, async (req, res, ctx) => {
|
||||
const { bucket, key } = parseDataLoaderKey(
|
||||
req.url.searchParams.get('items') as string,
|
||||
);
|
||||
expect(bucket).toEqual('Test.Mock.Thing');
|
||||
expect(key).toEqual('key');
|
||||
return res(ctx.text('{ invalid: json string }'));
|
||||
}),
|
||||
);
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
@@ -353,17 +364,14 @@ describe('Persistent Storage API', () => {
|
||||
});
|
||||
|
||||
it('should freeze the snapshot value', async () => {
|
||||
const storage = createPersistentStorage();
|
||||
const storage = createPersistentStorage({ namespace: 'freeze' });
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
const data = { foo: 'bar', baz: [{ foo: 'bar' }] };
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
`${mockBaseUrl}/buckets/:bucket/keys/:key`,
|
||||
async (_req, res, ctx) => {
|
||||
return res(ctx.json({ value: data }));
|
||||
},
|
||||
),
|
||||
rest.get(`${mockBaseUrl}/multi`, async (_req, res, ctx) => {
|
||||
return res(ctx.json([{ bucket: 'freeze', key: 'key', value: data }]));
|
||||
}),
|
||||
);
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
@@ -396,4 +404,112 @@ describe('Persistent Storage API', () => {
|
||||
snapshot.value.baz.push({ foo: 'buzz' });
|
||||
}).toThrow(/Cannot add property 1, object is not extensible/);
|
||||
});
|
||||
|
||||
it('should batch multiple calls into one', async () => {
|
||||
const storage = createPersistentStorage({ namespace: 'multiget' });
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
const selectedKeyNextHandlerCached = jest.fn();
|
||||
const data1 = { foo: 'bar1', baz: [{ foo: 'bar1' }] };
|
||||
const data2 = { foo: 'bar2', baz: [{ foo: 'bar2' }] };
|
||||
|
||||
let serverCalls = 0;
|
||||
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/multi`, async (req, res, ctx) => {
|
||||
++serverCalls;
|
||||
const result = req.url.searchParams
|
||||
.getAll('items')
|
||||
.map(item => parseDataLoaderKey(item))
|
||||
.map(({ key }) => {
|
||||
if (key === 'key1') {
|
||||
return { bucket: 'multiget', key, value: data1 };
|
||||
} else if (key === 'key2') {
|
||||
return { bucket: 'multiget', key, value: data2 };
|
||||
}
|
||||
return { bucket: 'multiget', key, error: new NotFoundError() };
|
||||
});
|
||||
|
||||
return res(ctx.json(result));
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
new Promise<void>(resolve => {
|
||||
storage.observe$('key1').subscribe({
|
||||
next: snapshot => {
|
||||
selectedKeyNextHandler(snapshot);
|
||||
if (snapshot.presence === 'present') {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
storage.snapshot('key1');
|
||||
}),
|
||||
|
||||
new Promise<void>(resolve => {
|
||||
storage.observe$('missing-key').subscribe({
|
||||
next: snapshot => {
|
||||
selectedKeyNextHandler(snapshot);
|
||||
if (snapshot.presence === 'absent') {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
storage.snapshot('missing-key');
|
||||
}),
|
||||
|
||||
new Promise<void>(resolve => {
|
||||
storage.observe$('key2').subscribe({
|
||||
next: snapshot => {
|
||||
selectedKeyNextHandler(snapshot);
|
||||
if (snapshot.presence === 'present') {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
storage.snapshot('key2');
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
|
||||
key: 'key1',
|
||||
presence: 'present',
|
||||
value: data1,
|
||||
});
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
|
||||
key: 'key2',
|
||||
presence: 'present',
|
||||
value: data2,
|
||||
});
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
|
||||
key: 'missing-key',
|
||||
presence: 'absent',
|
||||
});
|
||||
expect(mockErrorApi.post).not.toHaveBeenCalled();
|
||||
expect(serverCalls).toBe(1);
|
||||
|
||||
// Get key1 again, should use cached value
|
||||
await new Promise<void>(resolve => {
|
||||
storage.observe$('key1').subscribe({
|
||||
next: snapshot => {
|
||||
selectedKeyNextHandlerCached(snapshot);
|
||||
if (snapshot.presence === 'present') {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
storage.snapshot('key1');
|
||||
});
|
||||
|
||||
expect(selectedKeyNextHandlerCached).toHaveBeenCalledWith({
|
||||
key: 'key1',
|
||||
presence: 'present',
|
||||
value: data1,
|
||||
});
|
||||
expect(serverCalls).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,11 +23,19 @@ import {
|
||||
StorageApi,
|
||||
StorageValueSnapshot,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { deserializeError, ResponseError } from '@backstage/errors';
|
||||
import { JsonValue, Observable } from '@backstage/types';
|
||||
import { SignalApi, SignalSubscriber } from '@backstage/plugin-signals-react';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { UserSettingsSignal } from '@backstage/plugin-user-settings-common';
|
||||
import {
|
||||
isMultiUserSettingError,
|
||||
MultiUserSetting,
|
||||
parseDataLoaderKey,
|
||||
stringifyDataLoaderKey,
|
||||
UserSettingsSignal,
|
||||
} from '@backstage/plugin-user-settings-common';
|
||||
import DataLoader from 'dataloader';
|
||||
import { CacheMap } from './CacheMap';
|
||||
|
||||
const JSON_HEADERS = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
@@ -36,6 +44,9 @@ const JSON_HEADERS = {
|
||||
|
||||
const buckets = new Map<string, UserSettingsStorage>();
|
||||
|
||||
const DATALOADER_CACHE_TTL_MS = 2 * 1000; // 2 seconds cache
|
||||
const DATALOADER_WINDOW_MS = 10; // 10 ms
|
||||
|
||||
/**
|
||||
* An implementation of the storage API, that uses the user-settings backend to
|
||||
* persist the data in the DB.
|
||||
@@ -59,6 +70,7 @@ export class UserSettingsStorage implements StorageApi {
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly fallback: WebStorage;
|
||||
private readonly signalApi?: SignalApi;
|
||||
private readonly userSettingsLoader: DataLoader<string, any>;
|
||||
|
||||
private constructor(
|
||||
namespace: string,
|
||||
@@ -68,6 +80,7 @@ export class UserSettingsStorage implements StorageApi {
|
||||
identityApi: IdentityApi,
|
||||
fallback: WebStorage,
|
||||
signalApi?: SignalApi,
|
||||
userSettingsLoader?: DataLoader<string, any>,
|
||||
) {
|
||||
this.namespace = namespace;
|
||||
this.fetchApi = fetchApi;
|
||||
@@ -76,6 +89,27 @@ export class UserSettingsStorage implements StorageApi {
|
||||
this.identityApi = identityApi;
|
||||
this.fallback = fallback;
|
||||
this.signalApi = signalApi;
|
||||
|
||||
this.userSettingsLoader =
|
||||
userSettingsLoader ??
|
||||
new DataLoader<string, any>(
|
||||
async bucketAndKeyList => this.getMulti(bucketAndKeyList),
|
||||
{
|
||||
name: 'UserSettingsStorage.userSettingsLoader',
|
||||
cacheMap: new CacheMap<string, Promise<unknown>>(
|
||||
DATALOADER_CACHE_TTL_MS,
|
||||
),
|
||||
maxBatchSize: 100,
|
||||
batchScheduleFn: cb => setTimeout(cb, DATALOADER_WINDOW_MS),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private stringifyDataLoaderKey(key: string) {
|
||||
return stringifyDataLoaderKey(this.namespace, key);
|
||||
}
|
||||
private clearCacheKey(key: string) {
|
||||
this.userSettingsLoader.clear(this.stringifyDataLoaderKey(key));
|
||||
}
|
||||
|
||||
static create(options: {
|
||||
@@ -114,6 +148,8 @@ export class UserSettingsStorage implements StorageApi {
|
||||
this.errorApi,
|
||||
this.identityApi,
|
||||
this.fallback,
|
||||
this.signalApi,
|
||||
this.userSettingsLoader,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -132,6 +168,8 @@ export class UserSettingsStorage implements StorageApi {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
this.clearCacheKey(key);
|
||||
|
||||
this.notifyChanges({ key, presence: 'absent' });
|
||||
}
|
||||
|
||||
@@ -139,9 +177,12 @@ export class UserSettingsStorage implements StorageApi {
|
||||
if (!(await this.isSignedIn())) {
|
||||
await this.fallback.set(key, data);
|
||||
this.notifyChanges({ key, presence: 'present', value: data });
|
||||
this.clearCacheKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearCacheKey(key);
|
||||
|
||||
const fetchUrl = await this.getFetchUrl(key);
|
||||
|
||||
const response = await this.fetchApi.fetch(fetchUrl, {
|
||||
@@ -156,6 +197,12 @@ export class UserSettingsStorage implements StorageApi {
|
||||
|
||||
const { value } = await response.json();
|
||||
|
||||
this.userSettingsLoader.prime(this.stringifyDataLoaderKey(key), {
|
||||
key,
|
||||
presence: 'present',
|
||||
value,
|
||||
});
|
||||
|
||||
this.notifyChanges({ key, value, presence: 'present' });
|
||||
}
|
||||
|
||||
@@ -171,7 +218,9 @@ export class UserSettingsStorage implements StorageApi {
|
||||
|
||||
const updateSnapshot = () => {
|
||||
Promise.resolve()
|
||||
.then(() => this.get(key))
|
||||
.then(() =>
|
||||
this.userSettingsLoader.load(this.stringifyDataLoaderKey(key)),
|
||||
)
|
||||
.then(snapshot => subscriber.next(snapshot))
|
||||
.catch(error => this.errorApi.post(error));
|
||||
};
|
||||
@@ -206,19 +255,30 @@ export class UserSettingsStorage implements StorageApi {
|
||||
return { key, presence: 'unknown' };
|
||||
}
|
||||
|
||||
private async get<T extends JsonValue>(
|
||||
key: string,
|
||||
): Promise<StorageValueSnapshot<T>> {
|
||||
private async getMulti(
|
||||
bucketAndKeyList: readonly string[],
|
||||
): Promise<StorageValueSnapshot<JsonValue>[]> {
|
||||
if (bucketAndKeyList.length === 0) return [];
|
||||
|
||||
if (!(await this.isSignedIn())) {
|
||||
// This explicitly uses WebStorage, which we know is synchronous and doesn't return presence: unknown
|
||||
return this.fallback.snapshot(key);
|
||||
return bucketAndKeyList.map(bucketAndKey =>
|
||||
this.fallback.snapshot(parseDataLoaderKey(bucketAndKey).key),
|
||||
);
|
||||
}
|
||||
|
||||
const fetchUrl = await this.getFetchUrl(key);
|
||||
const response = await this.fetchApi.fetch(fetchUrl);
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('user-settings');
|
||||
const url = new URL(`${baseUrl}/multi`);
|
||||
for (const bucketAndKey of bucketAndKeyList) {
|
||||
url.searchParams.append('items', bucketAndKey);
|
||||
}
|
||||
const response = await this.fetchApi.fetch(url);
|
||||
|
||||
if (response.status === 404) {
|
||||
return { key, presence: 'absent' };
|
||||
return bucketAndKeyList.map(bucketAndKey => ({
|
||||
key: parseDataLoaderKey(bucketAndKey).key,
|
||||
presence: 'absent',
|
||||
}));
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -226,18 +286,37 @@ export class UserSettingsStorage implements StorageApi {
|
||||
}
|
||||
|
||||
try {
|
||||
const { value: rawValue } = await response.json();
|
||||
const value = JSON.parse(JSON.stringify(rawValue), (_key, val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
Object.freeze(val);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
const values = await response.json();
|
||||
|
||||
return { key, presence: 'present', value };
|
||||
return (values as MultiUserSetting[]).map(
|
||||
(setting): StorageValueSnapshot<JsonValue> => {
|
||||
if (isMultiUserSettingError(setting)) {
|
||||
if (setting.error.name === 'NotFoundError') {
|
||||
return {
|
||||
key: setting.key,
|
||||
presence: 'absent',
|
||||
};
|
||||
}
|
||||
throw deserializeError(setting.error);
|
||||
}
|
||||
return {
|
||||
key: setting.key,
|
||||
presence: 'present',
|
||||
value: JSON.parse(JSON.stringify(setting.value), (_key, val) => {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
Object.freeze(val);
|
||||
}
|
||||
return val;
|
||||
}),
|
||||
};
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
// If the value is not valid JSON, we return an unknown presence. This should never happen
|
||||
return { key, presence: 'absent' };
|
||||
return bucketAndKeyList.map(bucketAndKey => ({
|
||||
key: parseDataLoaderKey(bucketAndKey).key,
|
||||
presence: 'absent',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2291,7 +2291,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3":
|
||||
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3":
|
||||
version: 7.27.1
|
||||
resolution: "@babel/code-frame@npm:7.27.1"
|
||||
dependencies:
|
||||
@@ -2440,6 +2440,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/helper-plugin-utils@npm:^7.25.9":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/helper-plugin-utils@npm:7.25.9"
|
||||
checksum: 10/e347d87728b1ab10b6976d46403941c8f9008c045ea6d99997a7ffca7b852dc34b6171380f7b17edf94410e0857ff26f3a53d8618f11d73744db86e8ca9b8c64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/helper-replace-supers@npm:^7.25.0":
|
||||
version: 7.25.0
|
||||
resolution: "@babel/helper-replace-supers@npm:7.25.0"
|
||||
@@ -2615,7 +2622,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.27.1":
|
||||
"@babel/plugin-syntax-jsx@npm:^7.24.7":
|
||||
version: 7.24.7
|
||||
resolution: "@babel/plugin-syntax-jsx@npm:7.24.7"
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils": "npm:^7.24.7"
|
||||
peerDependencies:
|
||||
"@babel/core": ^7.0.0-0
|
||||
checksum: 10/a93516ae5b34868ab892a95315027d4e5e38e8bd1cfca6158f2974b0901cbb32bbe64ea10ad5b25f919ddc40c6d8113c4823372909c9c9922170c12b0b1acecb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-syntax-jsx@npm:^7.27.1":
|
||||
version: 7.27.1
|
||||
resolution: "@babel/plugin-syntax-jsx@npm:7.27.1"
|
||||
dependencies:
|
||||
@@ -2714,7 +2732,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.27.1":
|
||||
"@babel/plugin-syntax-typescript@npm:^7.24.7":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/plugin-syntax-typescript@npm:7.25.9"
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils": "npm:^7.25.9"
|
||||
peerDependencies:
|
||||
"@babel/core": ^7.0.0-0
|
||||
checksum: 10/0e9821e8ba7d660c36c919654e4144a70546942ae184e85b8102f2322451eae102cbfadbcadd52ce077a2b44b400ee52394c616feab7b5b9f791b910e933fd33
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-syntax-typescript@npm:^7.27.1":
|
||||
version: 7.27.1
|
||||
resolution: "@babel/plugin-syntax-typescript@npm:7.27.1"
|
||||
dependencies:
|
||||
@@ -2899,7 +2928,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5":
|
||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4":
|
||||
version: 7.28.4
|
||||
resolution: "@babel/types@npm:7.28.4"
|
||||
dependencies:
|
||||
"@babel/helper-string-parser": "npm:^7.27.1"
|
||||
"@babel/helper-validator-identifier": "npm:^7.27.1"
|
||||
checksum: 10/db50bf257aafa5d845ad16dae0587f57d596e4be4cbb233ea539976a4c461f9fbcc0bf3d37adae3f8ce5dcb4001462aa608f3558161258b585f6ce6ce21a2e45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.27.3, @babel/types@npm:^7.28.5":
|
||||
version: 7.28.5
|
||||
resolution: "@babel/types@npm:7.28.5"
|
||||
dependencies:
|
||||
@@ -7784,6 +7823,7 @@ __metadata:
|
||||
"@backstage/types": "workspace:^"
|
||||
"@types/express": "npm:^4.17.6"
|
||||
"@types/supertest": "npm:^2.0.8"
|
||||
already: "npm:^2.2.1"
|
||||
express: "npm:^4.22.0"
|
||||
express-promise-router: "npm:^4.1.0"
|
||||
knex: "npm:^3.0.0"
|
||||
@@ -7796,6 +7836,8 @@ __metadata:
|
||||
resolution: "@backstage/plugin-user-settings-common@workspace:plugins/user-settings-common"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -7826,6 +7868,8 @@ __metadata:
|
||||
"@testing-library/react": "npm:^16.0.0"
|
||||
"@testing-library/user-event": "npm:^14.0.0"
|
||||
"@types/react": "npm:^18.0.0"
|
||||
already: "npm:^2.2.1"
|
||||
dataloader: "npm:^2.0.0"
|
||||
msw: "npm:^1.0.0"
|
||||
react: "npm:^18.0.2"
|
||||
react-dom: "npm:^18.0.2"
|
||||
@@ -10308,6 +10352,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jest/expect-utils@npm:^29.7.0":
|
||||
version: 29.7.0
|
||||
resolution: "@jest/expect-utils@npm:29.7.0"
|
||||
dependencies:
|
||||
jest-get-type: "npm:^29.6.3"
|
||||
checksum: 10/ef8d379778ef574a17bde2801a6f4469f8022a46a5f9e385191dc73bb1fc318996beaed4513fbd7055c2847227a1bed2469977821866534593a6e52a281499ee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@jest/expect@npm:30.2.0":
|
||||
version: 30.2.0
|
||||
resolution: "@jest/expect@npm:30.2.0"
|
||||
@@ -13956,13 +14009,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@opentelemetry/semantic-conventions@npm:^1.24.0, @opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0, @opentelemetry/semantic-conventions@npm:^1.36.0, @opentelemetry/semantic-conventions@npm:^1.37.0":
|
||||
"@opentelemetry/semantic-conventions@npm:^1.24.0, @opentelemetry/semantic-conventions@npm:^1.33.0, @opentelemetry/semantic-conventions@npm:^1.36.0, @opentelemetry/semantic-conventions@npm:^1.37.0":
|
||||
version: 1.38.0
|
||||
resolution: "@opentelemetry/semantic-conventions@npm:1.38.0"
|
||||
checksum: 10/9d549f4896e900f644d5e70dd7142505daff88ed83c1cb7bcd976ac55e9496d4ddd686bb2815dd68655c739950514394c3b73ff51e53b2e4ff2d54a7f6d22521
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0":
|
||||
version: 1.34.0
|
||||
resolution: "@opentelemetry/semantic-conventions@npm:1.34.0"
|
||||
checksum: 10/1892b4cc69c9e00456c809604a980e32696563e96463ff5f9d07e72d5aca73836a7378090509f28f54445ac6e072d2343a888c9d64d9ce287198e899082ff7aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@opentelemetry/semantic-conventions@npm:~1.30.0":
|
||||
version: 1.30.0
|
||||
resolution: "@opentelemetry/semantic-conventions@npm:1.30.0"
|
||||
@@ -17624,7 +17684,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:^13.0.0, @sinonjs/fake-timers@npm:^13.0.1":
|
||||
"@sinonjs/fake-timers@npm:^13.0.0":
|
||||
version: 13.0.5
|
||||
resolution: "@sinonjs/fake-timers@npm:13.0.5"
|
||||
dependencies:
|
||||
@@ -17633,6 +17693,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:^13.0.1":
|
||||
version: 13.0.2
|
||||
resolution: "@sinonjs/fake-timers@npm:13.0.2"
|
||||
dependencies:
|
||||
"@sinonjs/commons": "npm:^3.0.1"
|
||||
checksum: 10/77cca5c548e2529931908c48ac375f162ee901bc52110197b4c470b2535c6c571f9ecd4fa12157f4d2ae174c5391f03940fb563a681a691fb44204a0ef3ded35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/samsam@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "@sinonjs/samsam@npm:8.0.0"
|
||||
@@ -17720,7 +17789,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@slack/types@npm:^2.11.0, @slack/types@npm:^2.13.0, @slack/types@npm:^2.14.0, @slack/types@npm:^2.18.0":
|
||||
"@slack/types@npm:^2.11.0, @slack/types@npm:^2.13.0, @slack/types@npm:^2.14.0":
|
||||
version: 2.14.0
|
||||
resolution: "@slack/types@npm:2.14.0"
|
||||
checksum: 10/fa24a113b88e087f899078504c2ba50ab9795f7c2dd1a2d95b28217a3af20e554494f9cc3b8c8ce173120990d98e19400c95369f9067cecfcc46c08b59d2a46f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@slack/types@npm:^2.18.0":
|
||||
version: 2.19.0
|
||||
resolution: "@slack/types@npm:2.19.0"
|
||||
checksum: 10/d5fbc227147b32a964072cb46e2c328b896507a374194532ddf594e18e4ccc2afe9f31f2b92d14ace43ef856b22979523e565616be2c87a2dc6c575d4f919fd0
|
||||
@@ -18378,7 +18454,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@smithy/signature-v4@npm:^4.1.0, @smithy/signature-v4@npm:^4.1.1":
|
||||
"@smithy/signature-v4@npm:^4.1.0":
|
||||
version: 4.2.4
|
||||
resolution: "@smithy/signature-v4@npm:4.2.4"
|
||||
dependencies:
|
||||
@@ -18394,6 +18470,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@smithy/signature-v4@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@smithy/signature-v4@npm:4.1.1"
|
||||
dependencies:
|
||||
"@smithy/is-array-buffer": "npm:^3.0.0"
|
||||
"@smithy/protocol-http": "npm:^4.1.1"
|
||||
"@smithy/types": "npm:^3.4.0"
|
||||
"@smithy/util-hex-encoding": "npm:^3.0.0"
|
||||
"@smithy/util-middleware": "npm:^3.0.4"
|
||||
"@smithy/util-uri-escape": "npm:^3.0.0"
|
||||
"@smithy/util-utf8": "npm:^3.0.0"
|
||||
tslib: "npm:^2.6.2"
|
||||
checksum: 10/f77d8b6cb384a0b9a0925ce6a693fbbcbf13c6f9ddf12cb9e1b39fb06452d001b17164d5e2618ea103edb427f2f1225d057827e2815e629b582115b2d533194f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@smithy/signature-v4@npm:^5.3.5":
|
||||
version: 5.3.5
|
||||
resolution: "@smithy/signature-v4@npm:5.3.5"
|
||||
@@ -18689,7 +18781,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@smithy/util-middleware@npm:^3.0.11, @smithy/util-middleware@npm:^3.0.4":
|
||||
"@smithy/util-middleware@npm:^3.0.11":
|
||||
version: 3.0.11
|
||||
resolution: "@smithy/util-middleware@npm:3.0.11"
|
||||
dependencies:
|
||||
@@ -18699,6 +18791,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@smithy/util-middleware@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "@smithy/util-middleware@npm:3.0.4"
|
||||
dependencies:
|
||||
"@smithy/types": "npm:^3.4.0"
|
||||
tslib: "npm:^2.6.2"
|
||||
checksum: 10/d358c8e5ab462b749b32421135f1660302de8b2a942e5cf92b63b1489e87d0fc57aa4cc578fee46a7192472946b06a898d98b1f01c8cf4b9fccc4ad489ffde5a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@smithy/util-middleware@npm:^4.2.5":
|
||||
version: 4.2.5
|
||||
resolution: "@smithy/util-middleware@npm:4.2.5"
|
||||
@@ -21004,14 +21106,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/express-serve-static-core@npm:*":
|
||||
version: 5.1.1
|
||||
resolution: "@types/express-serve-static-core@npm:5.1.1"
|
||||
version: 4.19.6
|
||||
resolution: "@types/express-serve-static-core@npm:4.19.6"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
"@types/qs": "npm:*"
|
||||
"@types/range-parser": "npm:*"
|
||||
"@types/send": "npm:*"
|
||||
checksum: 10/7f3d8cf7e68764c9f3e8f6a12825b69ccf5287347fc1c20b29803d4f08a4abc1153ae11d7258852c61aad50f62ef72d4c1b9c97092b0a90462c3dddec2f6026c
|
||||
checksum: 10/a2e00b6c5993f0dd63ada2239be81076fe0220314b9e9fde586e8946c9c09ce60f9a2dd0d74410ee2b5fd10af8c3e755a32bb3abf134533e2158142488995455
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -21036,7 +21138,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/express@npm:*, @types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.25, @types/express@npm:^4.17.6":
|
||||
"@types/express@npm:*, @types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.6":
|
||||
version: 4.17.23
|
||||
resolution: "@types/express@npm:4.17.23"
|
||||
dependencies:
|
||||
"@types/body-parser": "npm:*"
|
||||
"@types/express-serve-static-core": "npm:^4.17.33"
|
||||
"@types/qs": "npm:*"
|
||||
"@types/serve-static": "npm:*"
|
||||
checksum: 10/cf4d540bbd90801cdc79a46107b8873404698a7fd0c3e8dd42989d52d3bd7f5b8768672e54c20835e41e27349c319bb47a404ad14c0f8db0e9d055ba1cb8a05b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/express@npm:^4.17.25":
|
||||
version: 4.17.25
|
||||
resolution: "@types/express@npm:4.17.25"
|
||||
dependencies:
|
||||
@@ -21398,14 +21512,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/luxon@npm:^3.0.0":
|
||||
version: 3.7.1
|
||||
resolution: "@types/luxon@npm:3.7.1"
|
||||
checksum: 10/c7bc164c278393ea0be938f986c74b4cddfab9013b1aff4495b016f771ded1d5b7b7b4825b2c7f0b8799edce19c5f531c28ff434ab3dedf994ac2d99a20fd4c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/luxon@npm:~3.4.0":
|
||||
"@types/luxon@npm:^3.0.0, @types/luxon@npm:~3.4.0":
|
||||
version: 3.4.2
|
||||
resolution: "@types/luxon@npm:3.4.2"
|
||||
checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d
|
||||
@@ -21530,7 +21637,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-fetch@npm:^2.6.13, @types/node-fetch@npm:^2.6.4":
|
||||
"@types/node-fetch@npm:^2.6.13":
|
||||
version: 2.6.13
|
||||
resolution: "@types/node-fetch@npm:2.6.13"
|
||||
dependencies:
|
||||
@@ -21540,6 +21647,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-fetch@npm:^2.6.4":
|
||||
version: 2.6.12
|
||||
resolution: "@types/node-fetch@npm:2.6.12"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
form-data: "npm:^4.0.0"
|
||||
checksum: 10/8107c479da83a3114fcbfa882eba95ee5175cccb5e4dd53f737a96f2559ae6262f662176b8457c1656de09ec393cc7b20a266c077e4bfb21e929976e1cf4d0f9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node-forge@npm:^1.3.0":
|
||||
version: 1.3.14
|
||||
resolution: "@types/node-forge@npm:1.3.14"
|
||||
@@ -22058,6 +22175,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/serve-static@npm:*":
|
||||
version: 1.15.7
|
||||
resolution: "@types/serve-static@npm:1.15.7"
|
||||
dependencies:
|
||||
"@types/http-errors": "npm:*"
|
||||
"@types/node": "npm:*"
|
||||
"@types/send": "npm:*"
|
||||
checksum: 10/c5a7171d5647f9fbd096ed1a26105759f3153ccf683824d99fee4c7eb9cde2953509621c56a070dd9fb1159e799e86d300cbe4e42245ebc5b0c1767e8ca94a67
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/serve-static@npm:^1, @types/serve-static@npm:^1.15.5":
|
||||
version: 1.15.10
|
||||
resolution: "@types/serve-static@npm:1.15.10"
|
||||
@@ -22136,6 +22264,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/stack-utils@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@types/stack-utils@npm:2.0.0"
|
||||
checksum: 10/b3fbae25b073116977ecb5c67d22f14567b51a7792403b0bf46e5de8f29bde3bd4ec1626afb22065495ca7f1c699c8bd66720050c94b8f8f9bcefbee79d161fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/stack-utils@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@types/stack-utils@npm:2.0.3"
|
||||
@@ -22371,7 +22506,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yargs@npm:^17.0.33, @types/yargs@npm:^17.0.8":
|
||||
"@types/yargs@npm:^17.0.33":
|
||||
version: 17.0.35
|
||||
resolution: "@types/yargs@npm:17.0.35"
|
||||
dependencies:
|
||||
@@ -22380,6 +22515,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yargs@npm:^17.0.8":
|
||||
version: 17.0.12
|
||||
resolution: "@types/yargs@npm:17.0.12"
|
||||
dependencies:
|
||||
"@types/yargs-parser": "npm:*"
|
||||
checksum: 10/ffbbfad0c75cc058e0518f202e3651b9fb60c7c1325240cc72ac0a022da746a759ba3c6e0099152076fed5012fb694eb4685e4520c04dc8399c22bb81221bff5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yarnpkg__lockfile@npm:^1.1.4":
|
||||
version: 1.1.9
|
||||
resolution: "@types/yarnpkg__lockfile@npm:1.1.9"
|
||||
@@ -22447,6 +22591,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.35.0":
|
||||
version: 8.35.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.35.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.35.0"
|
||||
"@typescript-eslint/types": "npm:^8.35.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <5.9.0"
|
||||
checksum: 10/a9419da92231aa27f75078fcffab1d02398b50fdb7d5399775a414ba02570682b4b60cdfafb544a021b0dc2372f029c4195f5ae17c50deb11c25661b2ac18a74
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.48.1"
|
||||
@@ -22503,6 +22660,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0":
|
||||
version: 8.35.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <5.9.0"
|
||||
checksum: 10/4160928313ccbe8b169a009b9c1220826c7df7aab427f960c31f3b838931bc7a121ebee8040118481e4528e2e3cf1b26da047c6ac1d802ecff2ef7206026ea6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1"
|
||||
@@ -22544,6 +22710,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0":
|
||||
version: 8.35.0
|
||||
resolution: "@typescript-eslint/types@npm:8.35.0"
|
||||
checksum: 10/34b5e6da2c59ea84cd528608fff0cc14b102fd23f5517dfee4ef38c9372861d80b5bf92445c9679674f0a4f8dc4ded5066c1bca2bc5569c47515f94568984f35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/types@npm:8.48.1"
|
||||
@@ -22596,7 +22769,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.54.0, @typescript-eslint/typescript-estree@npm:^8.23.0":
|
||||
"@typescript-eslint/typescript-estree@npm:8.54.0":
|
||||
version: 8.54.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.54.0"
|
||||
dependencies:
|
||||
@@ -22615,6 +22788,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:^8.23.0":
|
||||
version: 8.35.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.35.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.35.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.35.0"
|
||||
"@typescript-eslint/types": "npm:8.35.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.35.0"
|
||||
debug: "npm:^4.3.4"
|
||||
fast-glob: "npm:^3.3.2"
|
||||
is-glob: "npm:^4.0.3"
|
||||
minimatch: "npm:^9.0.4"
|
||||
semver: "npm:^7.6.0"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <5.9.0"
|
||||
checksum: 10/4dff7c5a8853c8f4e30d35565c62d3ad5bf8445309bd465d94e9bca725853012bb9f58896a04207c30e10b6669511caac8c0f080ed781c93a3db81d5808195aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.48.1"
|
||||
@@ -22669,6 +22862,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.35.0":
|
||||
version: 8.35.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.35.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.35.0"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10/c0acb13aac3a2be5e82844f7d2e86137347efdd04661dbf9fa69ef04a19dd2f1eb2f1eb6bfbfbaada78a46884308d2c0e0b5d0d1a094c84f2dfb670b67ac2b3b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.48.1"
|
||||
@@ -24494,6 +24697,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"already@npm:^2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "already@npm:2.2.1"
|
||||
checksum: 10/1c55b50667c3dbe9d40716454d4a870f5758143061a0e39c0a7077eab2c6dbec116edf081796afb6f441462096bf68ef72a4daad074843a0d970527f29037ffe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"anser@npm:^2.1.1":
|
||||
version: 2.3.3
|
||||
resolution: "anser@npm:2.3.3"
|
||||
@@ -25287,7 +25497,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.7.4":
|
||||
"axios@npm:^1.0.0, axios@npm:^1.7.4":
|
||||
version: 1.12.2
|
||||
resolution: "axios@npm:1.12.2"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.6"
|
||||
form-data: "npm:^4.0.4"
|
||||
proxy-from-env: "npm:^1.1.0"
|
||||
checksum: 10/886a79770594eaad76493fecf90344b567bd956240609b5dcd09bd0afe8d3e6f1ad6d3257a93a483b6192b409d4b673d9515a34619e3e3ed1b2c0ec2a83b20ba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.11.0, axios@npm:^1.12.2, axios@npm:^1.13.0":
|
||||
version: 1.13.4
|
||||
resolution: "axios@npm:1.13.4"
|
||||
dependencies:
|
||||
@@ -28498,15 +28719,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3":
|
||||
version: 4.4.3
|
||||
resolution: "debug@npm:4.4.3"
|
||||
"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "debug@npm:4.4.0"
|
||||
dependencies:
|
||||
ms: "npm:^2.1.3"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad
|
||||
checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -28531,6 +28752,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:^4.3.7, debug@npm:^4.4.3":
|
||||
version: 4.4.3
|
||||
resolution: "debug@npm:4.4.3"
|
||||
dependencies:
|
||||
ms: "npm:^2.1.3"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debuglog@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "debuglog@npm:1.0.1"
|
||||
@@ -30674,13 +30907,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1":
|
||||
"eventsource-parser@npm:^3.0.0":
|
||||
version: 3.0.6
|
||||
resolution: "eventsource-parser@npm:3.0.6"
|
||||
checksum: 10/febf7058b9c2168ecbb33e92711a1646e06bd1568f60b6eb6a01a8bf9f8fcd29cc8320d57247059cacf657a296280159f21306d2e3ff33309a9552b2ef889387
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventsource-parser@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "eventsource-parser@npm:3.0.1"
|
||||
checksum: 10/2730c54c3cb47d55d2967f2ece843f9fc95d8a11c2fef6fece8d17d9080193cbe3cd9ac7b04a325977f63cbf8c1664fdd0512dec1aec601666a5c5bd8564b61f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventsource@npm:^3.0.2":
|
||||
version: 3.0.7
|
||||
resolution: "eventsource@npm:3.0.7"
|
||||
@@ -30989,7 +31229,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expect@npm:30.2.0, expect@npm:>28.1.3, expect@npm:^30.0.0":
|
||||
"expect@npm:30.2.0, expect@npm:^30.0.0":
|
||||
version: 30.2.0
|
||||
resolution: "expect@npm:30.2.0"
|
||||
dependencies:
|
||||
@@ -31003,6 +31243,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expect@npm:>28.1.3":
|
||||
version: 29.7.0
|
||||
resolution: "expect@npm:29.7.0"
|
||||
dependencies:
|
||||
"@jest/expect-utils": "npm:^29.7.0"
|
||||
jest-get-type: "npm:^29.6.3"
|
||||
jest-matcher-utils: "npm:^29.7.0"
|
||||
jest-message-util: "npm:^29.7.0"
|
||||
jest-util: "npm:^29.7.0"
|
||||
checksum: 10/63f97bc51f56a491950fb525f9ad94f1916e8a014947f8d8445d3847a665b5471b768522d659f5e865db20b6c2033d2ac10f35fcbd881a4d26407a4f6f18451a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"exponential-backoff@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "exponential-backoff@npm:3.1.1"
|
||||
@@ -32031,7 +32284,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:11.3.3, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0, fs-extra@npm:~11.3.0":
|
||||
"fs-extra@npm:11.3.3, fs-extra@npm:~11.3.0":
|
||||
version: 11.3.3
|
||||
resolution: "fs-extra@npm:11.3.3"
|
||||
dependencies:
|
||||
@@ -32065,6 +32318,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0":
|
||||
version: 11.3.1
|
||||
resolution: "fs-extra@npm:11.3.1"
|
||||
dependencies:
|
||||
graceful-fs: "npm:^4.2.0"
|
||||
jsonfile: "npm:^6.0.1"
|
||||
universalify: "npm:^2.0.0"
|
||||
checksum: 10/2b893213411b1da11f9b061ccb0bcff4d6dd66fe90aa8f5b1616219a5e7ca659da869f454ebd8e94aa21c58342730fb43a2e5c98b5c6c5124f0c54a4633f64b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:^7.0.1":
|
||||
version: 7.0.1
|
||||
resolution: "fs-extra@npm:7.0.1"
|
||||
@@ -32342,7 +32606,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
|
||||
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "get-intrinsic@npm:1.3.0"
|
||||
dependencies:
|
||||
call-bind-apply-helpers: "npm:^1.0.2"
|
||||
es-define-property: "npm:^1.0.1"
|
||||
es-errors: "npm:^1.3.0"
|
||||
es-object-atoms: "npm:^1.1.1"
|
||||
function-bind: "npm:^1.1.2"
|
||||
get-proto: "npm:^1.0.1"
|
||||
gopd: "npm:^1.2.0"
|
||||
has-symbols: "npm:^1.1.0"
|
||||
hasown: "npm:^2.0.2"
|
||||
math-intrinsics: "npm:^1.1.0"
|
||||
checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.2.2":
|
||||
version: 1.3.1
|
||||
resolution: "get-intrinsic@npm:1.3.1"
|
||||
dependencies:
|
||||
@@ -35549,7 +35831,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-diff@npm:^29.2.0":
|
||||
"jest-diff@npm:^29.2.0, jest-diff@npm:^29.7.0":
|
||||
version: 29.7.0
|
||||
resolution: "jest-diff@npm:29.7.0"
|
||||
dependencies:
|
||||
@@ -35649,6 +35931,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-matcher-utils@npm:^29.7.0":
|
||||
version: 29.7.0
|
||||
resolution: "jest-matcher-utils@npm:29.7.0"
|
||||
dependencies:
|
||||
chalk: "npm:^4.0.0"
|
||||
jest-diff: "npm:^29.7.0"
|
||||
jest-get-type: "npm:^29.6.3"
|
||||
pretty-format: "npm:^29.7.0"
|
||||
checksum: 10/981904a494299cf1e3baed352f8a3bd8b50a8c13a662c509b6a53c31461f94ea3bfeffa9d5efcfeb248e384e318c87de7e3baa6af0f79674e987482aa189af40
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-message-util@npm:30.2.0":
|
||||
version: 30.2.0
|
||||
resolution: "jest-message-util@npm:30.2.0"
|
||||
@@ -35666,6 +35960,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-message-util@npm:^29.7.0":
|
||||
version: 29.7.0
|
||||
resolution: "jest-message-util@npm:29.7.0"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.12.13"
|
||||
"@jest/types": "npm:^29.6.3"
|
||||
"@types/stack-utils": "npm:^2.0.0"
|
||||
chalk: "npm:^4.0.0"
|
||||
graceful-fs: "npm:^4.2.9"
|
||||
micromatch: "npm:^4.0.4"
|
||||
pretty-format: "npm:^29.7.0"
|
||||
slash: "npm:^3.0.0"
|
||||
stack-utils: "npm:^2.0.3"
|
||||
checksum: 10/31d53c6ed22095d86bab9d14c0fa70c4a92c749ea6ceece82cf30c22c9c0e26407acdfbdb0231435dc85a98d6d65ca0d9cbcd25cd1abb377fe945e843fb770b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jest-mock@npm:30.2.0":
|
||||
version: 30.2.0
|
||||
resolution: "jest-mock@npm:30.2.0"
|
||||
@@ -37865,14 +38176,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0":
|
||||
version: 3.7.2
|
||||
resolution: "luxon@npm:3.7.2"
|
||||
checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"luxon@npm:~3.5.0":
|
||||
"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0, luxon@npm:~3.5.0":
|
||||
version: 3.5.0
|
||||
resolution: "luxon@npm:3.5.0"
|
||||
checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f
|
||||
@@ -38870,7 +39174,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromatch@npm:^4.0.2, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8":
|
||||
"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8":
|
||||
version: 4.0.8
|
||||
resolution: "micromatch@npm:4.0.8"
|
||||
dependencies:
|
||||
@@ -41921,13 +42225,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-connection-string@npm:^2.10.1, pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0":
|
||||
"pg-connection-string@npm:^2.10.1":
|
||||
version: 2.10.1
|
||||
resolution: "pg-connection-string@npm:2.10.1"
|
||||
checksum: 10/8785cfac30f64a48f47031c24d7c5c53241fabb336d5be8f30ca06af0e16aea8d9647b645b4b44ed931717a47a0afb95ddb57e60b6af103767a4d8606dec22dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0":
|
||||
version: 2.7.0
|
||||
resolution: "pg-connection-string@npm:2.7.0"
|
||||
checksum: 10/68015a8874b7ca5dad456445e4114af3d2602bac2fdb8069315ecad0ff9660ec93259b9af7186606529ac4f6f72a06831e6f20897a689b16cc7fda7ca0e247fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-format@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "pg-format@npm:1.0.4"
|
||||
@@ -41951,7 +42262,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-protocol@npm:*, pg-protocol@npm:^1.11.0":
|
||||
"pg-protocol@npm:*":
|
||||
version: 1.8.0
|
||||
resolution: "pg-protocol@npm:1.8.0"
|
||||
checksum: 10/52f67d8161ae4afb1dbf96f6ad12a2ecf478dbb0b80baa239047cd562dee378961fd446f0a1cfc1fd323e052fbb3df47e886c5d9d86a2803a000d36682b29094
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-protocol@npm:^1.11.0":
|
||||
version: 1.11.0
|
||||
resolution: "pg-protocol@npm:1.11.0"
|
||||
checksum: 10/a70b1b4a3fc5b1be80dfdd65c829a149b8bd9df7488f9c47e0b51c9413aec5eb6da0a9ae9812891d74cd9f2ee90c0e391984a41b64603e7375fcbb9e07070b08
|
||||
@@ -46294,13 +46612,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shell-quote@npm:1.8.3, shell-quote@npm:^1.6.1, shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1":
|
||||
"shell-quote@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "shell-quote@npm:1.8.3"
|
||||
checksum: 10/5473e354637c2bd698911224129c9a8961697486cff1fb221f234d71c153fc377674029b0223d1d3c953a68d451d79366abfe53d1a0b46ee1f28eb9ade928f4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shell-quote@npm:^1.6.1, shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1":
|
||||
version: 1.8.1
|
||||
resolution: "shell-quote@npm:1.8.1"
|
||||
checksum: 10/af19ab5a1ec30cb4b2f91fd6df49a7442d5c4825a2e269b3712eded10eedd7f9efeaab96d57829880733fc55bcdd8e9b1d8589b4befb06667c731d08145e274d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shelljs@npm:^0.9.2":
|
||||
version: 0.9.2
|
||||
resolution: "shelljs@npm:0.9.2"
|
||||
@@ -46965,7 +47290,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.6":
|
||||
"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.3, stack-utils@npm:^2.0.6":
|
||||
version: 2.0.6
|
||||
resolution: "stack-utils@npm:2.0.6"
|
||||
dependencies:
|
||||
@@ -47904,7 +48229,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:^2.0.0, tar-fs@npm:^2.1.4":
|
||||
"tar-fs@npm:^2.0.0":
|
||||
version: 2.1.2
|
||||
resolution: "tar-fs@npm:2.1.2"
|
||||
dependencies:
|
||||
chownr: "npm:^1.1.1"
|
||||
mkdirp-classic: "npm:^0.5.2"
|
||||
pump: "npm:^3.0.0"
|
||||
tar-stream: "npm:^2.1.4"
|
||||
checksum: 10/623f7e8e58a43578ba7368002c3cc7e321f6d170053ac0691d95172dbc7daf5dcf4347eb061277627340870ce6cfda89f5a5d633cc274c41ae6d69f54a2374e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:^2.1.4":
|
||||
version: 2.1.4
|
||||
resolution: "tar-fs@npm:2.1.4"
|
||||
dependencies:
|
||||
@@ -49330,13 +49667,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:^7.16.0, undici@npm:^7.2.3":
|
||||
"undici@npm:^7.16.0":
|
||||
version: 7.19.2
|
||||
resolution: "undici@npm:7.19.2"
|
||||
checksum: 10/26a01804402e5f09d02a5d09607e42096828698dddbcf32132dd313af4fda99f3c04ed84d884e28528f945a5b77d87df5c01ec6c1bdc918fd28878cb35f1cb4a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:^7.2.3":
|
||||
version: 7.9.0
|
||||
resolution: "undici@npm:7.9.0"
|
||||
checksum: 10/fa92d3d9106612be566e79942174e00426c2e1f9a688fb86c8d1809c84a032c02fc57bd601d2051a45d94bbd312c50221644b58c8186c4f0d64f8ecc7f18b806
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"uni-global@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "uni-global@npm:1.0.0"
|
||||
@@ -50875,9 +51219,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3, ws@npm:^8.8.0":
|
||||
version: 8.19.0
|
||||
resolution: "ws@npm:8.19.0"
|
||||
"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.8.0":
|
||||
version: 8.18.3
|
||||
resolution: "ws@npm:8.18.3"
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ">=5.0.2"
|
||||
@@ -50886,7 +51230,7 @@ __metadata:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
checksum: 10/26e4901e93abaf73af9f26a93707c95b4845e91a7a347ec8c569e6e9be7f9df066f6c2b817b2d685544e208207898a750b78461e6e8d810c11a370771450c31b
|
||||
checksum: 10/725964438d752f0ab0de582cd48d6eeada58d1511c3f613485b5598a83680bedac6187c765b0fe082e2d8cc4341fc57707c813ae780feee82d0c5efe6a4c61b6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -50920,6 +51264,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.18.2, ws@npm:^8.18.3":
|
||||
version: 8.19.0
|
||||
resolution: "ws@npm:8.19.0"
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ">=5.0.2"
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
checksum: 10/26e4901e93abaf73af9f26a93707c95b4845e91a7a347ec8c569e6e9be7f9df066f6c2b817b2d685544e208207898a750b78461e6e8d810c11a370771450c31b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wsl-utils@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "wsl-utils@npm:0.1.0"
|
||||
|
||||
Reference in New Issue
Block a user