mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
Merge pull request #4658 from logto-io/gao-add-relation-routes
refactor(core): `addRelationRoutes()` for `SchemaRouter`
This commit is contained in:
commit
33e9a5d695
7 changed files with 283 additions and 76 deletions
|
@ -44,6 +44,7 @@
|
||||||
"@silverhand/essentials": "^2.8.4",
|
"@silverhand/essentials": "^2.8.4",
|
||||||
"@simplewebauthn/server": "^8.2.0",
|
"@simplewebauthn/server": "^8.2.0",
|
||||||
"@withtyped/client": "^0.7.22",
|
"@withtyped/client": "^0.7.22",
|
||||||
|
"camelcase": "^8.0.0",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"clean-deep": "^3.4.0",
|
"clean-deep": "^3.4.0",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
|
@ -73,7 +74,6 @@
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"p-retry": "^6.0.0",
|
"p-retry": "^6.0.0",
|
||||||
"pg-protocol": "^1.6.0",
|
"pg-protocol": "^1.6.0",
|
||||||
"pluralize": "^8.0.0",
|
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"redis": "^4.6.5",
|
"redis": "^4.6.5",
|
||||||
"roarr": "^7.11.0",
|
"roarr": "^7.11.0",
|
||||||
|
@ -101,7 +101,6 @@
|
||||||
"@types/koa__cors": "^4.0.0",
|
"@types/koa__cors": "^4.0.0",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/oidc-provider": "^8.0.0",
|
"@types/oidc-provider": "^8.0.0",
|
||||||
"@types/pluralize": "^0.0.31",
|
|
||||||
"@types/qrcode": "^1.5.2",
|
"@types/qrcode": "^1.5.2",
|
||||||
"@types/semver": "^7.3.12",
|
"@types/semver": "^7.3.12",
|
||||||
"@types/sinon": "^10.0.13",
|
"@types/sinon": "^10.0.13",
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
OrganizationRoles,
|
OrganizationRoles,
|
||||||
OrganizationScopes,
|
OrganizationScopes,
|
||||||
OrganizationRoleScopeRelations,
|
OrganizationRoleScopeRelations,
|
||||||
|
Users,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { type CommonQueryMethods } from 'slonik';
|
import { type CommonQueryMethods } from 'slonik';
|
||||||
|
|
||||||
|
@ -31,6 +32,8 @@ export default class OrganizationQueries extends SchemaQueries<
|
||||||
OrganizationRoles,
|
OrganizationRoles,
|
||||||
OrganizationScopes
|
OrganizationScopes
|
||||||
),
|
),
|
||||||
|
/** Queries for organization - user relations. */
|
||||||
|
users: new RelationQueries(this.pool, 'organization_user_relations', Organizations, Users),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(pool: CommonQueryMethods) {
|
constructor(pool: CommonQueryMethods) {
|
||||||
|
|
|
@ -51,7 +51,9 @@ export default function organizationRoleRoutes<T extends AuthedRouter>(
|
||||||
const router = new SchemaRouter(OrganizationRoles, actions, { disabled: { post: true } });
|
const router = new SchemaRouter(OrganizationRoles, actions, { disabled: { post: true } });
|
||||||
|
|
||||||
/** Allows to carry an initial set of scopes for creating a new organization role. */
|
/** Allows to carry an initial set of scopes for creating a new organization role. */
|
||||||
type CreateOrganizationRolePayload = Omit<CreateOrganizationRole, 'id'> & { scopeIds: string[] };
|
type CreateOrganizationRolePayload = Omit<CreateOrganizationRole, 'id'> & {
|
||||||
|
organizationScopeIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
const createGuard: z.ZodType<CreateOrganizationRolePayload, z.ZodTypeDef, unknown> =
|
const createGuard: z.ZodType<CreateOrganizationRolePayload, z.ZodTypeDef, unknown> =
|
||||||
OrganizationRoles.createGuard
|
OrganizationRoles.createGuard
|
||||||
|
@ -59,7 +61,7 @@ export default function organizationRoleRoutes<T extends AuthedRouter>(
|
||||||
id: true,
|
id: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
scopeIds: z.array(z.string()).default([]),
|
organizationScopeIds: z.array(z.string()).default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
|
@ -70,7 +72,7 @@ export default function organizationRoleRoutes<T extends AuthedRouter>(
|
||||||
status: [201, 422],
|
status: [201, 422],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const { scopeIds, ...data } = ctx.guard.body;
|
const { organizationScopeIds: scopeIds, ...data } = ctx.guard.body;
|
||||||
const role = await actions.post(data);
|
const role = await actions.post(data);
|
||||||
|
|
||||||
if (scopeIds.length > 0) {
|
if (scopeIds.length > 0) {
|
||||||
|
@ -83,63 +85,7 @@ export default function organizationRoleRoutes<T extends AuthedRouter>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// MARK: Role - scope relations routes
|
router.addRelationRoutes(OrganizationScopes, rolesScopes, 'scopes');
|
||||||
router.get(
|
|
||||||
'/:id/scopes',
|
|
||||||
koaGuard({
|
|
||||||
params: z.object({ id: z.string().min(1) }),
|
|
||||||
response: OrganizationScopes.guard.array(),
|
|
||||||
status: [200, 404],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const { id } = ctx.guard.params;
|
|
||||||
|
|
||||||
// Ensure that role exists
|
|
||||||
await actions.getById(id);
|
|
||||||
|
|
||||||
ctx.body = await rolesScopes.getEntries(OrganizationScopes, { organizationRoleId: id });
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
'/:id/scopes',
|
|
||||||
koaGuard({
|
|
||||||
params: z.object({ id: z.string().min(1) }),
|
|
||||||
body: z.object({ scopeIds: z.string().min(1).array().nonempty() }),
|
|
||||||
response: OrganizationScopes.guard.array(),
|
|
||||||
status: [200, 404, 422],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const {
|
|
||||||
params: { id },
|
|
||||||
body: { scopeIds },
|
|
||||||
} = ctx.guard;
|
|
||||||
|
|
||||||
await rolesScopes.insert(...scopeIds.map<[string, string]>((scopeId) => [id, scopeId]));
|
|
||||||
|
|
||||||
ctx.body = await rolesScopes.getEntries(OrganizationScopes, { organizationRoleId: id });
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.delete(
|
|
||||||
'/:id/scopes/:scopeId',
|
|
||||||
koaGuard({
|
|
||||||
params: z.object({ id: z.string().min(1), scopeId: z.string().min(1) }),
|
|
||||||
status: [204, 422],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const {
|
|
||||||
params: { id, scopeId },
|
|
||||||
} = ctx.guard;
|
|
||||||
|
|
||||||
await rolesScopes.delete({ organizationRoleId: id, organizationScopeId: scopeId });
|
|
||||||
|
|
||||||
ctx.status = 204;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
originalRouter.use(router.routes());
|
originalRouter.use(router.routes());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { type SchemaLike, type GeneratedSchema, type Guard } from '@logto/schemas';
|
import { type SchemaLike, type GeneratedSchema, type Guard } from '@logto/schemas';
|
||||||
import { generateStandardId, type OmitAutoSetFields } from '@logto/shared';
|
import { generateStandardId, type OmitAutoSetFields } from '@logto/shared';
|
||||||
import { type DeepPartial } from '@silverhand/essentials';
|
import { type DeepPartial } from '@silverhand/essentials';
|
||||||
|
import camelcase from 'camelcase';
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
import Router, { type IRouterParamContext } from 'koa-router';
|
import Router, { type IRouterParamContext } from 'koa-router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
@ -8,8 +9,31 @@ import { z } from 'zod';
|
||||||
import koaGuard from '#src/middleware/koa-guard.js';
|
import koaGuard from '#src/middleware/koa-guard.js';
|
||||||
import koaPagination, { type Pagination } from '#src/middleware/koa-pagination.js';
|
import koaPagination, { type Pagination } from '#src/middleware/koa-pagination.js';
|
||||||
|
|
||||||
|
import type RelationQueries from './RelationQueries.js';
|
||||||
import type SchemaQueries from './SchemaQueries.js';
|
import type SchemaQueries from './SchemaQueries.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the pathname for from a table name.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* tableToPathname('organization_role') // => 'organization-role'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
const tableToPathname = (tableName: string) => tableName.replaceAll('_', '-');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the camel case schema ID column name.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* camelCaseSchemaId({ tableSingular: 'organization' as const }) // => 'organizationId'
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const camelCaseSchemaId = <T extends { tableSingular: Table }, Table extends string>(schema: T) =>
|
||||||
|
`${camelcase(schema.tableSingular)}Id` as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actions configuration for a {@link SchemaRouter}. It contains the
|
* Actions configuration for a {@link SchemaRouter}. It contains the
|
||||||
* necessary functions to handle the CRUD operations for a schema.
|
* necessary functions to handle the CRUD operations for a schema.
|
||||||
|
@ -126,7 +150,7 @@ export default class SchemaRouter<
|
||||||
public readonly actions: SchemaActions<Key, CreateSchema, Schema>,
|
public readonly actions: SchemaActions<Key, CreateSchema, Schema>,
|
||||||
config: DeepPartial<SchemaRouterConfig> = {}
|
config: DeepPartial<SchemaRouterConfig> = {}
|
||||||
) {
|
) {
|
||||||
super({ prefix: '/' + schema.table.replaceAll('_', '-') });
|
super({ prefix: '/' + tableToPathname(schema.table) });
|
||||||
|
|
||||||
this.config = deepmerge<SchemaRouterConfig, DeepPartial<SchemaRouterConfig>>(
|
this.config = deepmerge<SchemaRouterConfig, DeepPartial<SchemaRouterConfig>>(
|
||||||
{
|
{
|
||||||
|
@ -220,4 +244,113 @@ export default class SchemaRouter<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add routes for relations between the current schema and another schema.
|
||||||
|
*
|
||||||
|
* The routes are:
|
||||||
|
*
|
||||||
|
* - `GET /:id/[pathname]`: Get the entities of the relation.
|
||||||
|
* - `POST /:id/[pathname]`: Add entities to the relation.
|
||||||
|
* - `DELETE /:id/[pathname]/:relationSchemaId`: Remove an entity from the relation set.
|
||||||
|
* The `:relationSchemaId` is the entity ID in the relation schema.
|
||||||
|
*
|
||||||
|
* The `[pathname]` is determined by the `pathname` parameter.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* The `POST /:id/[pathname]` route accepts a JSON body with the following format:
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* { "[relationSchemaIds]": ["id1", "id2", "id3"] }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The `[relationSchemaIds]` is the camel case of the relation schema's table name in
|
||||||
|
* singular form with `Ids` suffix. For example, if the relation schema's table name is
|
||||||
|
* `organization_roles`, the `[relationSchemaIds]` will be `organizationRoleIds`.
|
||||||
|
*
|
||||||
|
* @param relationSchema The schema of the relation to be added.
|
||||||
|
* @param relationQueries The queries for the relation.
|
||||||
|
* @param pathname The pathname of the relation. If not provided, it will be
|
||||||
|
* the camel case of the relation schema's table name.
|
||||||
|
* @see {@link RelationQueries} for the `relationQueries` configuration.
|
||||||
|
*/
|
||||||
|
addRelationRoutes<
|
||||||
|
RelationKey extends string,
|
||||||
|
RelationCreateSchema extends Partial<SchemaLike<RelationKey> & { id: string }>,
|
||||||
|
RelationSchema extends SchemaLike<RelationKey> & { id: string },
|
||||||
|
>(
|
||||||
|
relationSchema: GeneratedSchema<RelationKey, RelationCreateSchema, RelationSchema>,
|
||||||
|
relationQueries: RelationQueries<[typeof this.schema, typeof relationSchema]>,
|
||||||
|
pathname = tableToPathname(relationSchema.table)
|
||||||
|
) {
|
||||||
|
const columns = {
|
||||||
|
schemaId: camelCaseSchemaId(this.schema),
|
||||||
|
relationSchemaId: camelCaseSchemaId(relationSchema),
|
||||||
|
relationSchemaIds: camelCaseSchemaId(relationSchema) + 's',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.get(
|
||||||
|
`/:id/${pathname}`,
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ id: z.string().min(1) }),
|
||||||
|
response: relationSchema.guard.array(),
|
||||||
|
status: [200, 404],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id } = ctx.guard.params;
|
||||||
|
|
||||||
|
// Ensure that the main entry exists
|
||||||
|
await this.actions.getById(id);
|
||||||
|
|
||||||
|
ctx.body = await relationQueries.getEntries(relationSchema, {
|
||||||
|
[columns.schemaId]: id,
|
||||||
|
});
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.post(
|
||||||
|
`/:id/${pathname}`,
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ id: z.string().min(1) }),
|
||||||
|
body: z.object({ [columns.relationSchemaIds]: z.string().min(1).array().nonempty() }),
|
||||||
|
response: relationSchema.guard.array(),
|
||||||
|
status: [200, 404, 422],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const {
|
||||||
|
params: { id },
|
||||||
|
body: { [columns.relationSchemaIds]: relationIds },
|
||||||
|
} = ctx.guard;
|
||||||
|
|
||||||
|
await relationQueries.insert(
|
||||||
|
...(relationIds?.map<[string, string]>((relationId) => [id, relationId]) ?? [])
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.body = await relationQueries.getEntries(relationSchema, { [columns.schemaId]: id });
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.delete(
|
||||||
|
`/:id/${pathname}/:relationId`,
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ id: z.string().min(1), relationId: z.string().min(1) }),
|
||||||
|
status: [204, 422],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const {
|
||||||
|
params: { id, relationId },
|
||||||
|
} = ctx.guard;
|
||||||
|
|
||||||
|
await relationQueries.delete({
|
||||||
|
[columns.schemaId]: id,
|
||||||
|
[columns.relationSchemaId]: relationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.status = 204;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,14 @@ import { ApiFactory } from './factory.js';
|
||||||
|
|
||||||
class OrganizationRoleApi extends ApiFactory<
|
class OrganizationRoleApi extends ApiFactory<
|
||||||
OrganizationRole,
|
OrganizationRole,
|
||||||
{ name: string; description?: string; scopeIds?: string[] }
|
{ name: string; description?: string; organizationScopeIds?: string[] }
|
||||||
> {
|
> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('organization-roles');
|
super('organization-roles');
|
||||||
}
|
}
|
||||||
|
|
||||||
async addScopes(id: string, scopeIds: string[]): Promise<void> {
|
async addScopes(id: string, organizationScopeIds: string[]): Promise<void> {
|
||||||
await authedAdminApi.post(`${this.path}/${id}/scopes`, { json: { scopeIds } });
|
await authedAdminApi.post(`${this.path}/${id}/scopes`, { json: { organizationScopeIds } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getScopes(id: string): Promise<OrganizationScope[]> {
|
async getScopes(id: string): Promise<OrganizationScope[]> {
|
||||||
|
|
|
@ -35,8 +35,8 @@ describe('organization role APIs', () => {
|
||||||
scopeApi.create({ name: 'test' + randomId() }),
|
scopeApi.create({ name: 'test' + randomId() }),
|
||||||
scopeApi.create({ name: 'test' + randomId() }),
|
scopeApi.create({ name: 'test' + randomId() }),
|
||||||
]);
|
]);
|
||||||
const scopeIds = [scope1.id, scope2.id];
|
const organizationScopeIds = [scope1.id, scope2.id];
|
||||||
const role = await roleApi.create({ name, scopeIds });
|
const role = await roleApi.create({ name, organizationScopeIds });
|
||||||
|
|
||||||
expect(role).toStrictEqual(
|
expect(role).toStrictEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
142
pnpm-lock.yaml
142
pnpm-lock.yaml
|
@ -3178,6 +3178,9 @@ importers:
|
||||||
'@withtyped/client':
|
'@withtyped/client':
|
||||||
specifier: ^0.7.22
|
specifier: ^0.7.22
|
||||||
version: 0.7.22(zod@3.22.3)
|
version: 0.7.22(zod@3.22.3)
|
||||||
|
camelcase:
|
||||||
|
specifier: ^8.0.0
|
||||||
|
version: 8.0.0
|
||||||
chalk:
|
chalk:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.1.2
|
version: 5.1.2
|
||||||
|
@ -3265,9 +3268,6 @@ importers:
|
||||||
pg-protocol:
|
pg-protocol:
|
||||||
specifier: ^1.6.0
|
specifier: ^1.6.0
|
||||||
version: 1.6.0
|
version: 1.6.0
|
||||||
pluralize:
|
|
||||||
specifier: ^8.0.0
|
|
||||||
version: 8.0.0
|
|
||||||
qrcode:
|
qrcode:
|
||||||
specifier: ^1.5.3
|
specifier: ^1.5.3
|
||||||
version: 1.5.3
|
version: 1.5.3
|
||||||
|
@ -3344,9 +3344,6 @@ importers:
|
||||||
'@types/oidc-provider':
|
'@types/oidc-provider':
|
||||||
specifier: ^8.0.0
|
specifier: ^8.0.0
|
||||||
version: 8.0.0
|
version: 8.0.0
|
||||||
'@types/pluralize':
|
|
||||||
specifier: ^0.0.31
|
|
||||||
version: 0.0.31
|
|
||||||
'@types/qrcode':
|
'@types/qrcode':
|
||||||
specifier: ^1.5.2
|
specifier: ^1.5.2
|
||||||
version: 1.5.2
|
version: 1.5.2
|
||||||
|
@ -3367,7 +3364,7 @@ importers:
|
||||||
version: 8.44.0
|
version: 8.44.0
|
||||||
jest:
|
jest:
|
||||||
specifier: ^29.5.0
|
specifier: ^29.5.0
|
||||||
version: 29.5.0(@types/node@18.11.18)(ts-node@10.9.1)
|
version: 29.5.0(@types/node@18.11.18)
|
||||||
jest-matcher-specific-error:
|
jest-matcher-specific-error:
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
@ -6960,6 +6957,48 @@ packages:
|
||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@jest/core@29.5.0:
|
||||||
|
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
|
||||||
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
node-notifier:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@jest/console': 29.5.0
|
||||||
|
'@jest/reporters': 29.5.0
|
||||||
|
'@jest/test-result': 29.5.0
|
||||||
|
'@jest/transform': 29.5.0
|
||||||
|
'@jest/types': 29.5.0
|
||||||
|
'@types/node': 18.11.18
|
||||||
|
ansi-escapes: 4.3.2
|
||||||
|
chalk: 4.1.2
|
||||||
|
ci-info: 3.8.0
|
||||||
|
exit: 0.1.2
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
jest-changed-files: 29.5.0
|
||||||
|
jest-config: 29.5.0(@types/node@18.11.18)
|
||||||
|
jest-haste-map: 29.5.0
|
||||||
|
jest-message-util: 29.5.0
|
||||||
|
jest-regex-util: 29.4.3
|
||||||
|
jest-resolve: 29.5.0
|
||||||
|
jest-resolve-dependencies: 29.5.0
|
||||||
|
jest-runner: 29.5.0
|
||||||
|
jest-runtime: 29.5.0
|
||||||
|
jest-snapshot: 29.5.0
|
||||||
|
jest-util: 29.5.0
|
||||||
|
jest-validate: 29.5.0
|
||||||
|
jest-watcher: 29.5.0
|
||||||
|
micromatch: 4.0.5
|
||||||
|
pretty-format: 29.5.0
|
||||||
|
slash: 3.0.0
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
- ts-node
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@jest/core@29.5.0(ts-node@10.9.1):
|
/@jest/core@29.5.0(ts-node@10.9.1):
|
||||||
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
|
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
@ -10952,7 +10991,6 @@ packages:
|
||||||
/camelcase@8.0.0:
|
/camelcase@8.0.0:
|
||||||
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
|
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/caniuse-lite@1.0.30001486:
|
/caniuse-lite@1.0.30001486:
|
||||||
resolution: {integrity: sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==}
|
resolution: {integrity: sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==}
|
||||||
|
@ -14450,6 +14488,34 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jest-cli@29.5.0(@types/node@18.11.18):
|
||||||
|
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
|
||||||
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
node-notifier:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@jest/core': 29.5.0
|
||||||
|
'@jest/test-result': 29.5.0
|
||||||
|
'@jest/types': 29.5.0
|
||||||
|
chalk: 4.1.2
|
||||||
|
exit: 0.1.2
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
import-local: 3.1.0
|
||||||
|
jest-config: 29.5.0(@types/node@18.11.18)
|
||||||
|
jest-util: 29.5.0
|
||||||
|
jest-validate: 29.5.0
|
||||||
|
prompts: 2.4.2
|
||||||
|
yargs: 17.7.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- supports-color
|
||||||
|
- ts-node
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jest-cli@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
|
/jest-cli@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
|
||||||
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
|
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
@ -14478,6 +14544,45 @@ packages:
|
||||||
- ts-node
|
- ts-node
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jest-config@29.5.0(@types/node@18.11.18):
|
||||||
|
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
|
||||||
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '*'
|
||||||
|
ts-node: '>=9.0.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
ts-node:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/core': 7.20.2
|
||||||
|
'@jest/test-sequencer': 29.5.0
|
||||||
|
'@jest/types': 29.5.0
|
||||||
|
'@types/node': 18.11.18
|
||||||
|
babel-jest: 29.5.0(@babel/core@7.20.2)
|
||||||
|
chalk: 4.1.2
|
||||||
|
ci-info: 3.8.0
|
||||||
|
deepmerge: 4.3.1
|
||||||
|
glob: 7.2.3
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
jest-circus: 29.5.0
|
||||||
|
jest-environment-node: 29.5.0
|
||||||
|
jest-get-type: 29.4.3
|
||||||
|
jest-regex-util: 29.4.3
|
||||||
|
jest-resolve: 29.5.0
|
||||||
|
jest-runner: 29.5.0
|
||||||
|
jest-util: 29.5.0
|
||||||
|
jest-validate: 29.5.0
|
||||||
|
micromatch: 4.0.5
|
||||||
|
parse-json: 5.2.0
|
||||||
|
pretty-format: 29.5.0
|
||||||
|
slash: 3.0.0
|
||||||
|
strip-json-comments: 3.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jest-config@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
|
/jest-config@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
|
||||||
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
|
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
@ -14889,6 +14994,26 @@ packages:
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jest@29.5.0(@types/node@18.11.18):
|
||||||
|
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
|
||||||
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
node-notifier:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@jest/core': 29.5.0
|
||||||
|
'@jest/types': 29.5.0
|
||||||
|
import-local: 3.1.0
|
||||||
|
jest-cli: 29.5.0(@types/node@18.11.18)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- supports-color
|
||||||
|
- ts-node
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jest@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
|
/jest@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
|
||||||
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
|
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
|
@ -17317,6 +17442,7 @@ packages:
|
||||||
/pluralize@8.0.0:
|
/pluralize@8.0.0:
|
||||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/pngjs@5.0.0:
|
/pngjs@5.0.0:
|
||||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||||
|
|
Loading…
Reference in a new issue