Creation and deprecation status of schema coordinates - schema age filter (part 1) (#5224)

This commit is contained in:
Kamil Kisiela 2024-08-12 11:23:32 +02:00 committed by GitHub
parent b651e890c2
commit 8020419437
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 2707 additions and 204 deletions

View file

@ -130,6 +130,7 @@ module.exports = {
'prefer-destructuring': 'off', 'prefer-destructuring': 'off',
'prefer-const': 'off', 'prefer-const': 'off',
'no-useless-escape': 'off', 'no-useless-escape': 'off',
'no-inner-declarations': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-unnecessary-type-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',

View file

@ -36,7 +36,7 @@
"slonik": "30.4.4", "slonik": "30.4.4",
"strip-ansi": "7.1.0", "strip-ansi": "7.1.0",
"tslib": "2.6.3", "tslib": "2.6.3",
"vitest": "1.6.0", "vitest": "2.0.3",
"zod": "3.23.8" "zod": "3.23.8"
} }
} }

View file

@ -72,6 +72,15 @@ export function initSeed() {
} }
return { return {
async createDbConnection() {
const pool = await createConnectionPool();
return {
pool,
[Symbol.asyncDispose]: async () => {
await pool.end();
},
};
},
authenticate: authenticate, authenticate: authenticate,
generateEmail: () => userEmail(generateUnique()), generateEmail: () => userEmail(generateUnique()),
async createOwner() { async createOwner() {

View file

@ -0,0 +1,991 @@
import 'reflect-metadata';
import { sql, type CommonQueryMethods } from 'slonik';
/* eslint-disable no-process-env */
import { ProjectType, TargetAccessScope } from 'testkit/gql/graphql';
import { test } from 'vitest';
import { initSeed } from '../../../testkit/seed';
async function fetchCoordinates(db: CommonQueryMethods, target: { id: string }) {
const result = await db.query<{
coordinate: string;
created_in_version_id: string;
deprecated_in_version_id: string | null;
}>(sql`
SELECT coordinate, created_in_version_id, deprecated_in_version_id
FROM schema_coordinate_status WHERE target_id = ${target.id}
`);
return result.rows;
}
describe('schema cleanup tracker', () => {
test.concurrent('single', async ({ expect }) => {
const { publishSchema, target, createDbConnection } = await prepare();
// This API is soooooooooooo awkward xD
await using db = await createDbConnection();
const ver1 = await publishSchema(/* GraphQL */ `
type Query {
hello: String
}
`);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hello',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
]),
);
const ver2 = await publishSchema(/* GraphQL */ `
type Query {
hello: String
hi: String
}
`);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hello',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hi',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
]),
);
await publishSchema(/* GraphQL */ `
type Query {
hello: String
}
`);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hello',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.not.objectContaining({
coordinate: 'Query.hi',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
]),
);
const ver4 = await publishSchema(/* GraphQL */ `
type Query {
hello: String
hi: String
}
`);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hello',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hi',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
]),
);
const ver5 = await publishSchema(/* GraphQL */ `
type Query {
hello: String @deprecated(reason: "no longer needed")
bye: String
goodbye: String
hi: String @deprecated(reason: "no longer needed")
}
`);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hello',
created_in_version_id: ver1,
deprecated_in_version_id: ver5,
}),
expect.objectContaining({
coordinate: 'Query.bye',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.goodbye',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hi',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
]),
);
await publishSchema(/* GraphQL */ `
type Query {
hello: String
bye: String
hi: String @deprecated(reason: "no longer needed")
}
`);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hello',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.bye',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.not.objectContaining({
coordinate: 'Query.goodbye',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.hi',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
]),
);
});
test.concurrent('federation', async ({ expect }) => {
const { publishSchema, deleteSchema, target, createDbConnection } = await prepare(
ProjectType.Federation,
);
await using db = await createDbConnection();
const serviceFoo = {
name: 'foo',
url: 'https://api.com/foo',
};
const serviceBar = {
name: 'bar',
url: 'https://api.com/bar',
};
const ver1 = await publishSchema(
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
user(id: ID!): User
}
type User @key(fields: "id") {
id: ID!
name: String
}
`,
serviceFoo,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
]),
);
const ver2 = await publishSchema(
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
users: User
}
type User @key(fields: "id") {
id: ID!
pictureUrl: String
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
]),
);
await deleteSchema(serviceBar.name);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.not.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
expect.not.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
]),
);
const ver4 = await publishSchema(
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
users: User
}
type User @key(fields: "id") {
id: ID!
pictureUrl: String
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
]),
);
const ver5 = await publishSchema(
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
users: User @deprecated(reason: "no longer needed")
randomUser: User
admin: User
}
type User @key(fields: "id") {
id: ID!
pictureUrl: String @deprecated(reason: "no longer needed")
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
expect.objectContaining({
coordinate: 'Query.randomUser',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.admin',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
]),
);
await publishSchema(
/* GraphQL */ `
extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
type Query {
users: User
admin: User
}
type User @key(fields: "id") {
id: ID!
pictureUrl: String @deprecated(reason: "no longer needed")
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
expect.not.objectContaining({
coordinate: 'Query.randomUser',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.admin',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
]),
);
});
test.concurrent('stitching', async ({ expect }) => {
const { publishSchema, deleteSchema, target, createDbConnection } = await prepare(
ProjectType.Stitching,
);
await using db = await createDbConnection();
const serviceFoo = {
name: 'foo',
url: 'https://api.com/foo',
};
const serviceBar = {
name: 'bar',
url: 'https://api.com/bar',
};
const ver1 = await publishSchema(
/* GraphQL */ `
type Query {
user(id: ID!): User @merge
}
type User @key(selectionSet: "{ id }") {
id: ID!
name: String
}
`,
serviceFoo,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
]),
);
const ver2 = await publishSchema(
/* GraphQL */ `
type Query {
users: User
user(id: ID!): User @merge
}
type User @key(selectionSet: "{ id }") {
id: ID!
pictureUrl: String
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
]),
);
await deleteSchema(serviceBar.name);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.not.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
expect.not.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver2,
deprecated_in_version_id: null,
}),
]),
);
const ver4 = await publishSchema(
/* GraphQL */ `
type Query {
users: User
user(id: ID!): User @merge
}
type User @key(selectionSet: "{ id }") {
id: ID!
pictureUrl: String
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
]),
);
const ver5 = await publishSchema(
/* GraphQL */ `
type Query {
users: User @deprecated(reason: "no longer needed")
randomUser: User
admin: User
user(id: ID!): User @merge
}
type User @key(selectionSet: "{ id }") {
id: ID!
pictureUrl: String @deprecated(reason: "no longer needed")
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
expect.objectContaining({
coordinate: 'Query.randomUser',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.admin',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
]),
);
await publishSchema(
/* GraphQL */ `
type Query {
users: User
admin: User
user(id: ID!): User @merge
}
type User @key(selectionSet: "{ id }") {
id: ID!
pictureUrl: String @deprecated(reason: "no longer needed")
}
`,
serviceBar,
);
await expect(fetchCoordinates(db.pool, target)).resolves.toEqual(
expect.arrayContaining([
// foo
expect.objectContaining({
coordinate: 'Query',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.user.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.id',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.name',
created_in_version_id: ver1,
deprecated_in_version_id: null,
}),
// bar
expect.objectContaining({
coordinate: 'Query.users',
created_in_version_id: ver4,
deprecated_in_version_id: null,
}),
expect.not.objectContaining({
coordinate: 'Query.randomUser',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'Query.admin',
created_in_version_id: ver5,
deprecated_in_version_id: null,
}),
expect.objectContaining({
coordinate: 'User.pictureUrl',
created_in_version_id: ver4,
deprecated_in_version_id: ver5,
}),
]),
);
});
});
async function prepare(projectType: ProjectType = ProjectType.Single) {
const { createOwner, createDbConnection } = initSeed();
const { createOrg } = await createOwner();
const { createProject, organization } = await createOrg();
const { createToken, project, target } = await createProject(projectType);
const token = await createToken({
targetScopes: [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite],
projectScopes: [],
organizationScopes: [],
});
return {
createDbConnection,
async publishSchema(
sdl: string,
service?: {
name: string;
url: string;
},
) {
const result = await token
.publishSchema({ sdl, service: service?.name, url: service?.url })
.then(r => r.expectNoGraphQLErrors());
if (result.schemaPublish.__typename !== 'SchemaPublishSuccess') {
console.log(JSON.stringify(result.schemaPublish, null, 2));
throw new Error(`Expected schemaPublish success, got ${result.schemaPublish.__typename}`);
}
if (!result.schemaPublish.valid) {
throw new Error('Expected schema to be valid');
}
const version = await token.fetchLatestValidSchema();
const versionId = version.latestValidVersion?.id;
if (!versionId) {
throw new Error('Expected version id to be defined');
}
return versionId;
},
async deleteSchema(serviceName: string) {
const result = await token.deleteSchema(serviceName).then(r => r.expectNoGraphQLErrors());
if (result.schemaDelete.__typename !== 'SchemaDeleteSuccess') {
throw new Error(`Expected schemaDelete success, got ${result.schemaDelete.__typename}`);
}
if (!result.schemaDelete.valid) {
throw new Error('Expected schema to be valid');
}
const version = await token.fetchLatestValidSchema();
const versionId = version.latestValidVersion?.id;
if (!versionId) {
throw new Error('Expected version id to be defined');
}
return versionId;
},
organization,
project,
target,
};
}

View file

@ -100,7 +100,7 @@
"turbo": "1.13.4", "turbo": "1.13.4",
"typescript": "5.5.3", "typescript": "5.5.3",
"vite-tsconfig-paths": "4.3.2", "vite-tsconfig-paths": "4.3.2",
"vitest": "1.6.0" "vitest": "2.0.3"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View file

@ -57,7 +57,7 @@
"graphql": "16.9.0", "graphql": "16.9.0",
"graphql-ws": "5.16.0", "graphql-ws": "5.16.0",
"nock": "14.0.0-beta.7", "nock": "14.0.0-beta.7",
"vitest": "1.6.0", "vitest": "2.0.3",
"ws": "8.18.0" "ws": "8.18.0"
}, },
"publishConfig": { "publishConfig": {

View file

@ -110,7 +110,7 @@ test('should not interrupt the process', async () => {
test('should capture client name and version headers', async () => { test('should capture client name and version headers', async () => {
const clean = handleProcess(); const clean = handleProcess();
const fetchSpy = vi.fn<[RequestInfo | URL, options: RequestInit | undefined]>(async () => const fetchSpy = vi.fn(async (_input: string | URL | globalThis.Request, _init?: RequestInit) =>
Response.json({}, { status: 200 }), Response.json({}, { status: 200 }),
); );

View file

@ -59,7 +59,7 @@
"graphql": "16.9.0", "graphql": "16.9.0",
"nock": "14.0.0-beta.7", "nock": "14.0.0-beta.7",
"tslib": "2.6.3", "tslib": "2.6.3",
"vitest": "1.6.0" "vitest": "2.0.3"
}, },
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org", "registry": "https://registry.npmjs.org",

View file

@ -61,7 +61,7 @@
"graphql-ws": "5.16.0", "graphql-ws": "5.16.0",
"graphql-yoga": "5.6.0", "graphql-yoga": "5.6.0",
"nock": "14.0.0-beta.7", "nock": "14.0.0-beta.7",
"vitest": "1.6.0", "vitest": "2.0.3",
"ws": "8.18.0" "ws": "8.18.0"
}, },
"publishConfig": { "publishConfig": {

View file

@ -123,7 +123,7 @@ test('should not interrupt the process', async () => {
}, 1_000); }, 1_000);
test('should capture client name and version headers', async () => { test('should capture client name and version headers', async () => {
const fetchSpy = vi.fn<[RequestInfo | URL, options: RequestInit | undefined]>(async () => const fetchSpy = vi.fn(async (_input: string | URL | globalThis.Request, _init?: RequestInit) =>
Response.json({}, { status: 200 }), Response.json({}, { status: 200 }),
); );
const clean = handleProcess(); const clean = handleProcess();

View file

@ -27,6 +27,7 @@
"copyfiles": "2.4.1", "copyfiles": "2.4.1",
"dotenv": "16.4.5", "dotenv": "16.4.5",
"got": "14.4.1", "got": "14.4.1",
"graphql": "16.9.0",
"p-limit": "4.0.0", "p-limit": "4.0.0",
"pg-promise": "11.9.0", "pg-promise": "11.9.0",
"slonik": "30.4.4", "slonik": "30.4.4",

View file

@ -0,0 +1,496 @@
import {
buildSchema,
GraphQLFieldMap,
GraphQLSchema,
isEnumType,
isInputObjectType,
isInterfaceType,
isIntrospectionType,
isObjectType,
isScalarType,
isUnionType,
} from 'graphql';
import { sql, type CommonQueryMethods } from 'slonik';
import { env } from '../environment';
import type { MigrationExecutor } from '../pg-migrator';
export default {
name: '2024.07.23T09.36.00.schema-cleanup-tracker.ts',
async run({ connection }) {
await connection.query(sql`
CREATE TABLE IF NOT EXISTS "schema_coordinate_status" (
coordinate text NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
created_in_version_id UUID NOT NULL REFERENCES "schema_versions" ("id") ON DELETE CASCADE,
deprecated_at TIMESTAMPTZ,
deprecated_in_version_id UUID REFERENCES "schema_versions" ("id") ON DELETE CASCADE,
"target_id" UUID NOT NULL REFERENCES "targets" ("id") ON DELETE CASCADE,
PRIMARY KEY (coordinate, target_id)
);
CREATE INDEX IF NOT EXISTS idx_schema_coordinate_status_by_target_timestamp
ON schema_coordinate_status(
target_id,
created_at,
deprecated_at
);
CREATE INDEX IF NOT EXISTS idx_schema_coordinate_status_by_target_coordinate_timestamp
ON schema_coordinate_status(
target_id,
coordinate,
created_at,
deprecated_at
);
`);
if (env.isHiveCloud) {
console.log('Skipping schema coordinate status migration for hive cloud');
return;
}
const schemaVersionsTotal = await connection.oneFirst<number>(sql`
SELECT count(*) as total FROM schema_versions
`);
console.log(`Found ${schemaVersionsTotal} schema versions`);
if (schemaVersionsTotal > 1000) {
console.warn(
`[WARN] There are more than 1000 schema versions (${schemaVersionsTotal}). Skipping a data backfill.`,
);
return;
}
await schemaCoordinateStatusMigration(connection);
},
} satisfies MigrationExecutor;
type SchemaCoordinatesDiffResult = {
/**
* Coordinates that are in incoming but not in existing (including deprecated ones)
*/
added: Set<string>;
/**
* Coordinates that are deprecated in incoming, but were not deprecated in existing or non-existent
*/
deprecated: Set<string>;
};
function diffSchemaCoordinates(
existingSchema: GraphQLSchema,
incomingSchema: GraphQLSchema,
): SchemaCoordinatesDiffResult {
const before = getSchemaCoordinates(existingSchema);
const after = getSchemaCoordinates(incomingSchema);
const added = after.coordinates.difference(before.coordinates);
const deprecated = after.deprecated.difference(before.deprecated);
return {
added,
deprecated,
};
}
export async function schemaCoordinateStatusMigration(connection: CommonQueryMethods) {
// Fetch targets
const targetResult = await connection.query<{ id: string }>(sql`
SELECT id FROM targets WHERE ID NOT IN (SELECT target_id FROM schema_coordinate_status)
`);
console.log(`Found ${targetResult.rowCount} targets`);
let i = 0;
for await (const target of targetResult.rows) {
try {
console.log(`Processing target (${i++}/${targetResult.rowCount}) - ${target.id}`);
const latestSchema = await connection.maybeOne<{
id: string;
created_at: number;
is_composable: boolean;
sdl?: string;
previous_schema_version_id?: string;
}>(sql`
SELECT
id,
created_at,
is_composable,
previous_schema_version_id,
composite_schema_sdl as sdl
FROM schema_versions
WHERE target_id = ${target.id} AND is_composable = true
ORDER BY created_at DESC
LIMIT 1
`);
if (!latestSchema) {
console.log('[SKIPPING] No latest composable schema found for target %s', target.id);
continue;
}
if (!latestSchema.sdl) {
console.warn(
`[SKIPPING] No latest, composable schema with non-empty sdl found for target ${target.id}.`,
);
continue;
}
const schema = buildSchema(latestSchema.sdl, {
assumeValid: true,
assumeValidSDL: true,
});
const targetCoordinates = getSchemaCoordinates(schema);
// The idea here is to
// 1. start from the latest composable version.
// 2. create a list of coordinates that are in the latest version, all and deprecated.
// 3. navigate to the previous version and compare the coordinates.
// 4. if a coordinate is added, upsert it into the schema_coordinate_status and remove it from the list.
// 5. if a coordinate is deprecated, upsert it into the schema_coordinate_status and remove it from the list of deprecated coordinates.
// 6. if the list of coordinates is empty, stop the process.
// 7. if the previous version is not composable, skip it and continue with the next previous version.
// 8. if the previous version is not found, insert all remaining coordinates and stop the process. This step might create incorrect dates!
await processVersion(1, connection, targetCoordinates, target.id, {
schema,
versionId: latestSchema.id,
createdAt: latestSchema.created_at,
previousVersionId: latestSchema.previous_schema_version_id ?? null,
});
} catch (error) {
console.error(`Error processing target ${target.id}`);
console.error(error);
}
}
}
function getSchemaCoordinates(schema: GraphQLSchema): {
coordinates: Set<string>;
deprecated: Set<string>;
} {
const coordinates = new Set<string>();
const deprecated = new Set<string>();
const typeMap = schema.getTypeMap();
for (const typeName in typeMap) {
const typeDefinition = typeMap[typeName];
if (isIntrospectionType(typeDefinition)) {
continue;
}
coordinates.add(typeName);
if (isObjectType(typeDefinition) || isInterfaceType(typeDefinition)) {
visitSchemaCoordinatesOfGraphQLFieldMap(
typeName,
typeDefinition.getFields(),
coordinates,
deprecated,
);
} else if (isInputObjectType(typeDefinition)) {
const fieldMap = typeDefinition.getFields();
for (const fieldName in fieldMap) {
const fieldDefinition = fieldMap[fieldName];
coordinates.add(`${typeName}.${fieldName}`);
if (fieldDefinition.deprecationReason) {
deprecated.add(`${typeName}.${fieldName}`);
}
}
} else if (isUnionType(typeDefinition)) {
for (const member of typeDefinition.getTypes()) {
coordinates.add(`${typeName}.${member.name}`);
}
} else if (isEnumType(typeDefinition)) {
const values = typeDefinition.getValues();
for (const value of values) {
coordinates.add(`${typeName}.${value.name}`);
if (value.deprecationReason) {
deprecated.add(`${typeName}.${value.name}`);
}
}
} else if (isScalarType(typeDefinition)) {
//
} else {
throw new Error(`Unsupported type kind ${typeName}`);
}
}
return {
coordinates,
deprecated,
};
}
function visitSchemaCoordinatesOfGraphQLFieldMap(
typeName: string,
fieldMap: GraphQLFieldMap<any, any>,
coordinates: Set<string>,
deprecated: Set<string>,
) {
for (const fieldName in fieldMap) {
const fieldDefinition = fieldMap[fieldName];
coordinates.add(`${typeName}.${fieldName}`);
if (fieldDefinition.deprecationReason) {
deprecated.add(`${typeName}.${fieldName}`);
}
for (const arg of fieldDefinition.args) {
coordinates.add(`${typeName}.${fieldName}.${arg.name}`);
if (arg.deprecationReason) {
deprecated.add(`${typeName}.${fieldName}.${arg.name}`);
}
}
}
}
async function insertRemainingCoordinates(
connection: CommonQueryMethods,
targetId: string,
targetCoordinates: {
coordinates: Set<string>;
deprecated: Set<string>;
},
versionId: string,
createdAt: number,
) {
if (targetCoordinates.coordinates.size === 0) {
return;
}
const pgDate = new Date(createdAt).toISOString();
// Deprecated only the coordinates that are still in the queue
const remainingDeprecated = targetCoordinates.deprecated.intersection(
targetCoordinates.coordinates,
);
console.log(
`Adding remaining ${targetCoordinates.coordinates.size} coordinates for target ${targetId}`,
);
await connection.query(sql`
INSERT INTO schema_coordinate_status
( target_id, coordinate, created_at, created_in_version_id )
SELECT * FROM ${sql.unnest(
Array.from(targetCoordinates.coordinates).map(coordinate => [
targetId,
coordinate,
pgDate,
versionId,
]),
['uuid', 'text', 'date', 'uuid'],
)}
ON CONFLICT (target_id, coordinate)
DO UPDATE SET created_at = ${pgDate}, created_in_version_id = ${versionId}
`);
if (remainingDeprecated.size) {
console.log(
`Deprecating remaining ${remainingDeprecated.size} coordinates for target ${targetId}`,
);
await connection.query(sql`
INSERT INTO schema_coordinate_status
( target_id, coordinate, created_at, created_in_version_id, deprecated_at, deprecated_in_version_id )
SELECT * FROM ${sql.unnest(
Array.from(remainingDeprecated).map(coordinate => [
targetId,
coordinate,
pgDate,
versionId,
pgDate,
versionId,
]),
['uuid', 'text', 'date', 'uuid', 'date', 'uuid'],
)}
ON CONFLICT (target_id, coordinate)
DO UPDATE SET deprecated_at = ${pgDate}, deprecated_in_version_id = ${versionId}
`);
// there will be a conflict, because we are going from deprecated to added order.
}
}
async function processVersion(
depth: number,
connection: CommonQueryMethods,
targetCoordinates: {
coordinates: Set<string>;
deprecated: Set<string>;
},
targetId: string,
after: {
schema: GraphQLSchema;
versionId: string;
createdAt: number;
previousVersionId: string | null;
},
): Promise<void> {
console.log(`Processing target %s at depth %s - version`, targetId, depth, after.versionId);
const previousVersionId = after.previousVersionId;
if (!previousVersionId) {
// Seems like there is no previous version.
console.log(
`[END] No previous version found. Inserting all remaining coordinates for ${targetId}`,
);
await insertRemainingCoordinates(
connection,
targetId,
targetCoordinates,
after.versionId,
after.createdAt,
);
return;
}
const versionBefore = await connection.maybeOne<{
id: string;
sdl?: string;
previous_schema_version_id?: string;
created_at: number;
is_composable: boolean;
}>(sql`
SELECT
id,
composite_schema_sdl as sdl,
previous_schema_version_id,
created_at,
is_composable
FROM schema_versions
WHERE id = ${previousVersionId} AND target_id = ${targetId}
`);
if (!versionBefore) {
console.error(
`[ERROR] No schema found for version ${previousVersionId}. Inserting all remaining coordinates for ${targetId}`,
);
await insertRemainingCoordinates(
connection,
targetId,
targetCoordinates,
after.versionId,
after.createdAt,
);
return;
}
if (!versionBefore.is_composable) {
// Skip non-composable schemas and continue with the previous version.
return processVersion(depth + 1, connection, targetCoordinates, targetId, {
schema: after.schema,
versionId: after.versionId,
createdAt: after.createdAt,
previousVersionId: versionBefore.previous_schema_version_id ?? null,
});
}
if (!versionBefore.sdl) {
console.error(
`[ERROR] No SDL found for version ${previousVersionId}. Inserting all remaining coordinates for ${targetId}`,
);
await insertRemainingCoordinates(
connection,
targetId,
targetCoordinates,
after.versionId,
after.createdAt,
);
return;
}
const before: {
schema: GraphQLSchema;
versionId: string;
createdAt: number;
previousVersionId: string | null;
} = {
schema: buildSchema(versionBefore.sdl, {
assumeValid: true,
assumeValidSDL: true,
}),
versionId: versionBefore.id,
createdAt: versionBefore.created_at,
previousVersionId: versionBefore.previous_schema_version_id ?? null,
};
const diff = diffSchemaCoordinates(before.schema, after.schema);
// We don't have to track undeprecated or deleted coordinates
// as we only want to represent the current state of the schema.
const added: string[] = [];
const deprecated: string[] = [];
const deleteAdded = new Set<string>();
const deleteDeprecated = new Set<string>();
for (const coordinate of diff.added) {
if (targetCoordinates.coordinates.has(coordinate)) {
added.push(coordinate);
// We found a schema version that added a coordinate, so we don't have to look further
deleteAdded.add(coordinate);
}
}
for (const coordinate of diff.deprecated) {
if (targetCoordinates.deprecated.has(coordinate)) {
deprecated.push(coordinate);
deleteDeprecated.add(coordinate);
}
}
const datePG = new Date(after.createdAt).toISOString();
if (added.length) {
console.log(`Adding ${added.length} coordinates for target ${targetId}`);
await connection.query(sql`
INSERT INTO schema_coordinate_status
( target_id, coordinate, created_at, created_in_version_id )
SELECT * FROM ${sql.unnest(
added.map(coordinate => [targetId, coordinate, datePG, after.versionId]),
['uuid', 'text', 'date', 'uuid'],
)}
ON CONFLICT (target_id, coordinate)
DO UPDATE SET created_at = ${datePG}, created_in_version_id = ${after.versionId}
`);
// there will be a conflict, because we are going from deprecated to added order.
}
if (deprecated.length) {
console.log(`deprecating ${deprecated.length} coordinates for target ${targetId}`);
await connection.query(sql`
INSERT INTO schema_coordinate_status
( target_id, coordinate, created_at, created_in_version_id, deprecated_at, deprecated_in_version_id )
SELECT * FROM ${sql.unnest(
deprecated.map(coordinate => [
targetId,
coordinate,
datePG,
after.versionId,
datePG,
after.versionId,
]),
['uuid', 'text', 'date', 'uuid', 'date', 'uuid'],
)}
ON CONFLICT (target_id, coordinate)
DO UPDATE SET deprecated_at = ${datePG}, deprecated_in_version_id = ${after.versionId}
`);
// there will be a conflict, because we are going from deprecated to added order.
}
// Remove coordinates that were added in this diff.
// We don't need to look for them in previous versions.
for (const coordinate of deleteAdded) {
targetCoordinates.coordinates.delete(coordinate);
}
// Remove coordinates that were deprecated in this diff.
// To avoid marking them as deprecated later on.
for (const coordinate of deleteDeprecated) {
targetCoordinates.deprecated.delete(coordinate);
}
if (deleteAdded.size) {
console.log(`Deleted ${deleteAdded.size} coordinates from the stack`);
console.log(`Coordinates in queue: ${targetCoordinates.coordinates.size}`);
}
return processVersion(depth + 1, connection, targetCoordinates, targetId, before);
}

View file

@ -1,5 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { createPool } from 'slonik'; import { createPool } from 'slonik';
import { schemaCoordinateStatusMigration } from './actions/2024.07.23T09.36.00.schema-cleanup-tracker';
import { migrateClickHouse } from './clickhouse'; import { migrateClickHouse } from './clickhouse';
import { createConnectionString } from './connection-string'; import { createConnectionString } from './connection-string';
import { env } from './environment'; import { env } from './environment';
@ -13,9 +14,21 @@ const slonik = await createPool(createConnectionString(env.postgres), {
// This is used by production build of this package. // This is used by production build of this package.
// We are building a "cli" out of the package, so we need a workaround to pass the command to run. // We are building a "cli" out of the package, so we need a workaround to pass the command to run.
console.log('Running the UP migrations'); // This is only used for GraphQL Hive Cloud to perform a long running migration.
// eslint-disable-next-line no-process-env
if (process.env.SCHEMA_COORDINATE_STATUS_MIGRATION === '1') {
try {
console.log('Running the SCHEMA_COORDINATE_STATUS_MIGRATION');
await schemaCoordinateStatusMigration(slonik);
process.exit(0);
} catch (error) {
console.error(error);
process.exit(1);
}
}
try { try {
console.log('Running the UP migrations');
await runPGMigrations({ slonik }); await runPGMigrations({ slonik });
if (env.clickhouse) { if (env.clickhouse) {
await migrateClickHouse(env.isClickHouseMigrator, env.isHiveCloud, env.clickhouse); await migrateClickHouse(env.isClickHouseMigrator, env.isHiveCloud, env.clickhouse);

View file

@ -66,6 +66,7 @@ import migration_2024_04_09T10_10_00_check_approval_comment from './actions/2024
import migration_2024_06_11T10_10_00_ms_teams_webhook from './actions/2024.06.11T10-10-00.ms-teams-webhook'; import migration_2024_06_11T10_10_00_ms_teams_webhook from './actions/2024.06.11T10-10-00.ms-teams-webhook';
import migration_2024_07_16T13_44_00_oidc_only_access from './actions/2024.07.16T13-44-00.oidc-only-access'; import migration_2024_07_16T13_44_00_oidc_only_access from './actions/2024.07.16T13-44-00.oidc-only-access';
import migration_2024_07_17T00_00_00_app_deployments from './actions/2024.07.17T00-00-00.app-deployments'; import migration_2024_07_17T00_00_00_app_deployments from './actions/2024.07.17T00-00-00.app-deployments';
import migration_2024_07_23T_09_36_00_schema_cleanup_tracker from './actions/2024.07.23T09.36.00.schema-cleanup-tracker';
import { runMigrations } from './pg-migrator'; import { runMigrations } from './pg-migrator';
export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string }) => export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string }) =>
@ -140,5 +141,6 @@ export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string })
migration_2024_06_11T10_10_00_ms_teams_webhook, migration_2024_06_11T10_10_00_ms_teams_webhook,
migration_2024_07_16T13_44_00_oidc_only_access, migration_2024_07_16T13_44_00_oidc_only_access,
migration_2024_07_17T00_00_00_app_deployments, migration_2024_07_17T00_00_00_app_deployments,
migration_2024_07_23T_09_36_00_schema_cleanup_tracker,
], ],
}); });

View file

@ -0,0 +1,246 @@
import assert from 'node:assert';
import { describe, test } from 'node:test';
import { sql } from 'slonik';
import { createStorage } from '../../services/storage/src/index';
import { initMigrationTestingEnvironment } from './utils/testkit';
await describe('migration: schema-cleanup-tracker', async () => {
await test('schema coordinates backfill', async () => {
const { db, runTo, complete, done, seed, connectionString } =
await initMigrationTestingEnvironment();
const storage = await createStorage(connectionString, 1);
try {
// Run migrations all the way to the point before the one we are testing
await runTo('2024.07.17T00-00-00.app-deployments.ts');
// Seed the database with some data (schema_sdl, supergraph_sdl, composite_schema_sdl)
const admin = await seed.user({
user: {
name: 'test1',
email: 'test1@test.com',
},
});
const organization = await seed.organization({
organization: {
name: 'org-1',
},
user: admin,
});
const project = await seed.project({
project: {
name: 'project-1',
type: 'SINGLE',
},
organization,
});
const target = await seed.target({
target: {
name: 'target-1',
},
project,
});
async function createVersion(
schema: string,
previousSchemaVersionId: string | null,
): Promise<string> {
const logId = await db.oneFirst<string>(sql`
INSERT INTO schema_log
(
author,
service_name,
service_url,
commit,
sdl,
project_id,
target_id,
metadata,
action
)
VALUES
(
${'Kamil'},
${null},
${null},
${'random'},
${schema},
${project.id},
${target.id},
${null},
'PUSH'
)
RETURNING id
`);
const versionId = await db.oneFirst<string>(sql`
INSERT INTO schema_versions
(
record_version,
is_composable,
target_id,
action_id,
base_schema,
has_persisted_schema_changes,
previous_schema_version_id,
diff_schema_version_id,
composite_schema_sdl,
supergraph_sdl,
schema_composition_errors,
github_repository,
github_sha,
tags,
has_contract_composition_errors,
conditional_breaking_change_metadata
)
VALUES
(
'2024-01-10',
${true},
${target.id},
${logId},
${null},
${true},
${previousSchemaVersionId},
${previousSchemaVersionId},
${schema},
${null},
${null},
${null},
${null},
${null},
${false},
${null}
)
RETURNING id
`);
return versionId;
}
const schemas = [
// [0]
// first
/* GraphQL */ `
type Query {
hello: String
}
`,
// [1]
// second
/* GraphQL */ `
type Query {
hello: String
hi: String
}
`,
// [2]
// third
/* GraphQL */ `
type Query {
hello: String
}
`,
// [3]
// fourth
/* GraphQL */ `
type Query {
hello: String
hi: String
}
`,
// [4]
// fifth
/* GraphQL */ `
type Query {
hello: String @deprecated(reason: "no longer needed")
bye: String
goodbye: String
hi: String @deprecated(reason: "no longer needed")
}
`,
// [5]
// sixth
/* GraphQL */ `
type Query {
hello: String
bye: String
hi: String @deprecated(reason: "no longer needed")
}
`,
];
// insert schema versions
let previousSchemaVersionId: string | null = null;
for await (const schema of schemas) {
const versionId = await createVersion(schema, previousSchemaVersionId);
previousSchemaVersionId = versionId;
}
// Run the remaining migrations
await complete();
// check that coordinates are correct
const versions = await db.manyFirst<string>(sql`
SELECT id FROM schema_versions WHERE target_id = ${target.id} ORDER BY created_at ASC
`);
const coordinates = await db.many<{
coordinate: string;
created_in_version_id: string;
deprecated_in_version_id: string | null;
}>(sql`
SELECT * FROM schema_coordinate_status WHERE target_id = ${target.id}
`);
assert.strictEqual(versions.length, 6);
const queryType = coordinates.find(c => c.coordinate === 'Query');
const helloField = coordinates.find(c => c.coordinate === 'Query.hello');
const hiField = coordinates.find(c => c.coordinate === 'Query.hi');
const byeField = coordinates.find(c => c.coordinate === 'Query.bye');
const goodbyeField = coordinates.find(c => c.coordinate === 'Query.goodbye');
assert.ok(queryType, 'Query type not found');
assert.ok(helloField, 'Query.hello field not found');
assert.ok(hiField, 'Query.hi field not found');
assert.ok(byeField, 'Query.bye field not found');
// Query
// was create in the first version
// never deprecated
assert.strictEqual(queryType.created_in_version_id, versions[0]);
assert.strictEqual(queryType.deprecated_in_version_id, null);
// Query.hello
// was created in the first version,
// deprecated in fifth
// undeprecated in the sixth
assert.strictEqual(helloField.created_in_version_id, versions[0]);
assert.strictEqual(helloField.deprecated_in_version_id, null);
// Query.hi
// was created in the second version
// removed in the third
// added back in the fourth
// deprecated in the fifth
assert.strictEqual(hiField.created_in_version_id, versions[3]);
assert.strictEqual(hiField.deprecated_in_version_id, versions[4]);
// Query.bye
// was created in the fifth version
assert.strictEqual(byeField.created_in_version_id, versions[4]);
// Query.goodbye
// was created in the fifth version
// removed in the sixth
assert.ok(!goodbyeField, 'Query.goodbye field should not be found');
} finally {
await done();
await storage.destroy();
}
});
});

View file

@ -2,3 +2,4 @@ import './2023.02.22T09.27.02.delete-personal-org.test';
import './2023.09.25T15.23.00.github-check-with-project-name.test'; import './2023.09.25T15.23.00.github-check-with-project-name.test';
import './2023.11.20T10-00-00.organization-member-roles.test'; import './2023.11.20T10-00-00.organization-member-roles.test';
import './2024.01.26T00.00.01.schema-check-purging.test'; import './2024.01.26T00.00.01.schema-check-purging.test';
import './2024.07.23T09.36.00.schema-cleanup-tracker.test';

View file

@ -65,7 +65,7 @@
"slonik": "30.4.4", "slonik": "30.4.4",
"supertokens-node": "15.2.1", "supertokens-node": "15.2.1",
"tslib": "2.6.3", "tslib": "2.6.3",
"vitest": "1.6.0", "vitest": "2.0.3",
"zod": "3.23.8", "zod": "3.23.8",
"zod-validation-error": "3.3.0" "zod-validation-error": "3.3.0"
} }

View file

@ -0,0 +1,371 @@
import 'reflect-metadata';
import { buildSchema } from 'graphql';
import { describe, expect, test } from 'vitest';
import { diffSchemaCoordinates } from './inspector';
describe('diffSchemaCoordinates', () => {
test('should return empty arrays when schemas are equal', () => {
const schema = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
`);
const result = diffSchemaCoordinates(schema, schema);
expect(result).toMatchInlineSnapshot(`
{
added: Set {},
deleted: Set {},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
test('field becomes deprecated', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
goodbye: String
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String @deprecated
goodbye: String
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {},
deleted: Set {},
deprecated: Set {
Query.hello,
},
undeprecated: Set {},
}
`);
});
test('added field is deprecated', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
hi: String @deprecated
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {
Query.hi,
},
deleted: Set {},
deprecated: Set {
Query.hi,
},
undeprecated: Set {},
}
`);
});
test('field is deleted', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
goodbye: String
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hi: String
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {
Query.hi,
},
deleted: Set {
Query.goodbye,
},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
test('deprecated field is deleted', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
goodbye: String @deprecated
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {},
deleted: Set {
Query.goodbye,
},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
test('deprecated field is undeprecated', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
hi: String @deprecated
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
hi: String
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {},
deleted: Set {},
deprecated: Set {},
undeprecated: Set {
Query.hi,
},
}
`);
});
test('deprecated field is undeprecated, undeprecated field is deprecated', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String @deprecated(reason: "no longer needed")
bye: String
goodbye: String
hi: String @deprecated(reason: "no longer needed")
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
bye: String
hi: String @deprecated(reason: "no longer needed")
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {},
deleted: Set {
Query.goodbye,
},
deprecated: Set {},
undeprecated: Set {
Query.hello,
},
}
`);
});
test('removed a deprecated field', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
goodbye: String @deprecated
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {},
deleted: Set {
Query.goodbye,
},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
test('added and removed an argument on deprecated and non-deprecated fields', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello(lang: String): String
hi: String @deprecated
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String @deprecated
hi(lang: String): String
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {
Query.hi.lang,
},
deleted: Set {
Query.hello.lang,
},
deprecated: Set {
Query.hello,
},
undeprecated: Set {
Query.hi,
},
}
`);
});
test('added removed enum members', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
enum Role {
ADMIN
USER
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
enum Role {
ANONYMOUS
USER
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {
Role.ANONYMOUS,
},
deleted: Set {
Role.ADMIN,
},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
test('added removed union members', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
union Account = Admin | User
type Admin {
id: ID
}
type User {
id: ID
}
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
union Account = Anonymous | User
type Anonymous {
ip: String
}
type User {
id: ID
}
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {
Account.Anonymous,
Anonymous,
Anonymous.ip,
},
deleted: Set {
Account.Admin,
Admin,
Admin.id,
},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
test('added removed scalars', () => {
const before = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
scalar GOODBYE
`);
const after = buildSchema(/* GraphQL */ `
type Query {
hello: String
}
scalar HELLO
`);
const result = diffSchemaCoordinates(before, after);
expect(result).toMatchInlineSnapshot(`
{
added: Set {
HELLO,
},
deleted: Set {
GOODBYE,
},
deprecated: Set {},
undeprecated: Set {},
}
`);
});
});

View file

@ -1,8 +1,18 @@
import { type GraphQLSchema } from 'graphql'; import {
GraphQLFieldMap,
isEnumType,
isInputObjectType,
isInterfaceType,
isIntrospectionType,
isObjectType,
isScalarType,
isUnionType,
type GraphQLSchema,
} from 'graphql';
import { Injectable, Scope } from 'graphql-modules'; import { Injectable, Scope } from 'graphql-modules';
import { Change, ChangeType, diff } from '@graphql-inspector/core'; import { Change, ChangeType, diff } from '@graphql-inspector/core';
import { traceFn } from '@hive/service-common'; import { traceFn } from '@hive/service-common';
import { HiveSchemaChangeModel, SchemaChangeType } from '@hive/storage'; import { HiveSchemaChangeModel } from '@hive/storage';
import { Logger } from '../../shared/providers/logger'; import { Logger } from '../../shared/providers/logger';
@Injectable({ @Injectable({
@ -21,7 +31,7 @@ export class Inspector {
'hive.diff.changes.count': result.length, 'hive.diff.changes.count': result.length,
}), }),
}) })
async diff(existing: GraphQLSchema, incoming: GraphQLSchema): Promise<Array<SchemaChangeType>> { async diff(existing: GraphQLSchema, incoming: GraphQLSchema) {
this.logger.debug('Comparing Schemas'); this.logger.debug('Comparing Schemas');
const changes = await diff(existing, incoming); const changes = await diff(existing, incoming);
@ -120,3 +130,130 @@ function matchChange<R, T extends ChangeType>(
return pattern[change.type]?.(change); return pattern[change.type]?.(change);
} }
} }
export type SchemaCoordinatesDiffResult = {
/**
* Coordinates that are in incoming but not in existing (including deprecated ones)
*/
added: Set<string>;
/**
* Coordinates that are in existing but not in incoming (including deprecated ones)
*/
deleted: Set<string>;
/**
* Coordinates that are deprecated in incoming, but were not deprecated in existing or non-existent
*/
deprecated: Set<string>;
/**
* Coordinates that exists in incoming and are not deprecated in incoming, but were deprecated in existing
*/
undeprecated: Set<string>;
};
export function diffSchemaCoordinates(
existingSchema: GraphQLSchema | null,
incomingSchema: GraphQLSchema,
): SchemaCoordinatesDiffResult {
const before = existingSchema
? getSchemaCoordinates(existingSchema)
: { coordinates: new Set<string>(), deprecated: new Set<string>() };
const after = getSchemaCoordinates(incomingSchema);
const added = after.coordinates.difference(before.coordinates);
const deleted = before.coordinates.difference(after.coordinates);
const deprecated = after.deprecated.difference(before.deprecated);
const undeprecated = before.deprecated
.difference(after.deprecated)
.intersection(after.coordinates);
return {
added,
deleted,
deprecated,
undeprecated,
};
}
export function getSchemaCoordinates(schema: GraphQLSchema): {
coordinates: Set<string>;
deprecated: Set<string>;
} {
const coordinates = new Set<string>();
const deprecated = new Set<string>();
const typeMap = schema.getTypeMap();
for (const typeName in typeMap) {
const typeDefinition = typeMap[typeName];
if (isIntrospectionType(typeDefinition)) {
continue;
}
coordinates.add(typeName);
if (isObjectType(typeDefinition) || isInterfaceType(typeDefinition)) {
visitSchemaCoordinatesOfGraphQLFieldMap(
typeName,
typeDefinition.getFields(),
coordinates,
deprecated,
);
} else if (isInputObjectType(typeDefinition)) {
const fieldMap = typeDefinition.getFields();
for (const fieldName in fieldMap) {
const fieldDefinition = fieldMap[fieldName];
coordinates.add(`${typeName}.${fieldName}`);
if (fieldDefinition.deprecationReason) {
deprecated.add(`${typeName}.${fieldName}`);
}
}
} else if (isUnionType(typeDefinition)) {
coordinates.add(typeName);
for (const member of typeDefinition.getTypes()) {
coordinates.add(`${typeName}.${member.name}`);
}
} else if (isEnumType(typeDefinition)) {
const values = typeDefinition.getValues();
for (const value of values) {
coordinates.add(`${typeName}.${value.name}`);
if (value.deprecationReason) {
deprecated.add(`${typeName}.${value.name}`);
}
}
} else if (isScalarType(typeDefinition)) {
coordinates.add(typeName);
} else {
throw new Error(`Unsupported type kind ${typeName}`);
}
}
return {
coordinates,
deprecated,
};
}
function visitSchemaCoordinatesOfGraphQLFieldMap(
typeName: string,
fieldMap: GraphQLFieldMap<any, any>,
coordinates: Set<string>,
deprecated: Set<string>,
) {
for (const fieldName in fieldMap) {
const fieldDefinition = fieldMap[fieldName];
coordinates.add(`${typeName}.${fieldName}`);
if (fieldDefinition.deprecationReason) {
deprecated.add(`${typeName}.${fieldName}`);
}
for (const arg of fieldDefinition.args) {
coordinates.add(`${typeName}.${fieldName}.${arg.name}`);
if (arg.deprecationReason) {
deprecated.add(`${typeName}.${fieldName}.${arg.name}`);
}
}
}
}

View file

@ -347,6 +347,11 @@ export class CompositeLegacyModel {
messages, messages,
changes, changes,
breakingChanges: breakingChanges ?? null, breakingChanges: breakingChanges ?? null,
coordinatesDiff:
diffCheck.result?.coordinatesDiff ??
diffCheck.reason?.coordinatesDiff ??
diffCheck.data?.coordinatesDiff ??
null,
compositionErrors, compositionErrors,
schema: incoming, schema: incoming,
schemas, schemas,
@ -372,6 +377,7 @@ export class CompositeLegacyModel {
code: PublishFailureReasonCode.BreakingChanges, code: PublishFailureReasonCode.BreakingChanges,
changes: diffCheck.reason.all ?? [], changes: diffCheck.reason.all ?? [],
breakingChanges: diffCheck.reason.breaking ?? [], breakingChanges: diffCheck.reason.breaking ?? [],
coordinatesDiff: diffCheck.reason?.coordinatesDiff ?? null,
}); });
} }

View file

@ -483,6 +483,11 @@ export class CompositeModel {
composable: compositionCheck.status === 'completed', composable: compositionCheck.status === 'completed',
initial: latestVersion === null, initial: latestVersion === null,
changes: diffCheck.result?.all ?? diffCheck.reason?.all ?? null, changes: diffCheck.result?.all ?? diffCheck.reason?.all ?? null,
coordinatesDiff:
diffCheck.result?.coordinatesDiff ??
diffCheck.reason?.coordinatesDiff ??
diffCheck.data?.coordinatesDiff ??
null,
messages, messages,
breakingChanges: null, breakingChanges: null,
compositionErrors: compositionCheck.reason?.errors ?? null, compositionErrors: compositionCheck.reason?.errors ?? null,
@ -688,6 +693,11 @@ export class CompositeModel {
...composablePartial, ...composablePartial,
changes, changes,
breakingChanges, breakingChanges,
coordinatesDiff:
diffCheck.result?.coordinatesDiff ??
diffCheck.reason?.coordinatesDiff ??
diffCheck.data?.coordinatesDiff ??
null,
compositionErrors: compositionCheck.reason?.errors ?? [], compositionErrors: compositionCheck.reason?.errors ?? [],
supergraph: compositionCheck.result?.supergraph ?? null, supergraph: compositionCheck.result?.supergraph ?? null,
tags: compositionCheck.result?.tags ?? null, tags: compositionCheck.result?.tags ?? null,

View file

@ -2,14 +2,15 @@ import { PushedCompositeSchema, SingleSchema } from 'packages/services/api/src/s
import type { CheckPolicyResponse } from '@hive/policy'; import type { CheckPolicyResponse } from '@hive/policy';
import { CompositionFailureError } from '@hive/schema'; import { CompositionFailureError } from '@hive/schema';
import type { SchemaChangeType, SchemaCompositionError } from '@hive/storage'; import type { SchemaChangeType, SchemaCompositionError } from '@hive/storage';
import { type Contract, type ValidContractVersion } from '../contracts'; import type { Contract, ValidContractVersion } from '../contracts';
import { import type { SchemaCoordinatesDiffResult } from '../inspector';
import type {
ContractCompositionResult, ContractCompositionResult,
ContractCompositionSuccess, ContractCompositionSuccess,
RegistryChecks,
SchemaDiffResult, SchemaDiffResult,
SchemaDiffSkip, SchemaDiffSkip,
SchemaDiffSuccess, SchemaDiffSuccess,
type RegistryChecks,
} from '../registry-checks'; } from '../registry-checks';
export const SchemaPublishConclusion = { export const SchemaPublishConclusion = {
@ -207,6 +208,7 @@ export type SchemaPublishFailureReason =
code: (typeof PublishFailureReasonCode)['BreakingChanges']; code: (typeof PublishFailureReasonCode)['BreakingChanges'];
breakingChanges: Array<SchemaChangeType>; breakingChanges: Array<SchemaChangeType>;
changes: Array<SchemaChangeType>; changes: Array<SchemaChangeType>;
coordinatesDiff: SchemaCoordinatesDiffResult;
}; };
type ContractResult = { type ContractResult = {
@ -223,6 +225,7 @@ type SchemaPublishSuccess = {
state: { state: {
composable: boolean; composable: boolean;
initial: boolean; initial: boolean;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
changes: Array<SchemaChangeType> | null; changes: Array<SchemaChangeType> | null;
messages: string[] | null; messages: string[] | null;
breakingChanges: Array<{ breakingChanges: Array<{
@ -277,6 +280,7 @@ export type SchemaDeleteSuccess = {
schemas: PushedCompositeSchema[]; schemas: PushedCompositeSchema[];
breakingChanges: Array<SchemaChangeType> | null; breakingChanges: Array<SchemaChangeType> | null;
compositionErrors: Array<SchemaCompositionError> | null; compositionErrors: Array<SchemaCompositionError> | null;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
supergraph: string | null; supergraph: string | null;
tags: null | Array<string>; tags: null | Array<string>;
contracts: null | Array<ContractResult>; contracts: null | Array<ContractResult>;

View file

@ -279,6 +279,11 @@ export class SingleLegacyModel {
messages, messages,
changes, changes,
breakingChanges: breakingChanges ?? null, breakingChanges: breakingChanges ?? null,
coordinatesDiff:
diffCheck.result?.coordinatesDiff ??
diffCheck.reason?.coordinatesDiff ??
diffCheck.data?.coordinatesDiff ??
null,
compositionErrors, compositionErrors,
schema: incoming, schema: incoming,
schemas, schemas,
@ -304,6 +309,7 @@ export class SingleLegacyModel {
code: PublishFailureReasonCode.BreakingChanges, code: PublishFailureReasonCode.BreakingChanges,
changes: diffCheck.reason.all ?? [], changes: diffCheck.reason.all ?? [],
breakingChanges: diffCheck.reason.breaking ?? [], breakingChanges: diffCheck.reason.breaking ?? [],
coordinatesDiff: diffCheck.reason?.coordinatesDiff ?? null,
}); });
} }

View file

@ -307,6 +307,11 @@ export class SingleModel {
changes: diffCheck.result?.all ?? diffCheck.reason?.all ?? null, changes: diffCheck.result?.all ?? diffCheck.reason?.all ?? null,
messages, messages,
breakingChanges: null, breakingChanges: null,
coordinatesDiff:
diffCheck.result?.coordinatesDiff ??
diffCheck.reason?.coordinatesDiff ??
diffCheck.data?.coordinatesDiff ??
null,
compositionErrors: compositionCheck.reason?.errors ?? null, compositionErrors: compositionCheck.reason?.errors ?? null,
schema: incoming, schema: incoming,
schemas, schemas,

View file

@ -24,7 +24,7 @@ import type {
SingleSchema, SingleSchema,
} from './../../../shared/entities'; } from './../../../shared/entities';
import { Logger } from './../../shared/providers/logger'; import { Logger } from './../../shared/providers/logger';
import { Inspector } from './inspector'; import { diffSchemaCoordinates, Inspector, SchemaCoordinatesDiffResult } from './inspector';
import { SchemaCheckWarning } from './models/shared'; import { SchemaCheckWarning } from './models/shared';
import { extendWithBase, isCompositeSchema, SchemaHelper } from './schema-helper'; import { extendWithBase, isCompositeSchema, SchemaHelper } from './schema-helper';
@ -37,7 +37,7 @@ export type ConditionalBreakingChangeDiffConfig = {
// The reason why I'm using `result` and `reason` instead of just `data` for both: // The reason why I'm using `result` and `reason` instead of just `data` for both:
// https://bit.ly/hive-check-result-data // https://bit.ly/hive-check-result-data
export type CheckResult<C = unknown, F = unknown> = export type CheckResult<C = unknown, F = unknown, S = unknown> =
| { | {
status: 'completed'; status: 'completed';
result: C; result: C;
@ -48,6 +48,7 @@ export type CheckResult<C = unknown, F = unknown> =
} }
| { | {
status: 'skipped'; status: 'skipped';
data?: S;
}; };
type Schemas = [SingleSchema] | PushedCompositeSchema[]; type Schemas = [SingleSchema] | PushedCompositeSchema[];
@ -134,6 +135,7 @@ type SchemaDiffFailure = {
breaking: Array<SchemaChangeType> | null; breaking: Array<SchemaChangeType> | null;
safe: Array<SchemaChangeType> | null; safe: Array<SchemaChangeType> | null;
all: Array<SchemaChangeType> | null; all: Array<SchemaChangeType> | null;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
}; };
result?: never; result?: never;
}; };
@ -144,6 +146,7 @@ export type SchemaDiffSuccess = {
breaking: Array<SchemaChangeType> | null; breaking: Array<SchemaChangeType> | null;
safe: Array<SchemaChangeType> | null; safe: Array<SchemaChangeType> | null;
all: Array<SchemaChangeType> | null; all: Array<SchemaChangeType> | null;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
}; };
reason?: never; reason?: never;
}; };
@ -412,32 +415,39 @@ export class RegistryChecks {
/** Settings for fetching conditional breaking changes. */ /** Settings for fetching conditional breaking changes. */
conditionalBreakingChangeConfig: null | ConditionalBreakingChangeDiffConfig; conditionalBreakingChangeConfig: null | ConditionalBreakingChangeDiffConfig;
}) { }) {
if (args.existingSdl == null || args.incomingSdl == null) { let existingSchema: GraphQLSchema | null = null;
this.logger.debug('Skip diff check due to either existing or incoming SDL being absent.'); let incomingSchema: GraphQLSchema | null = null;
try {
existingSchema = args.existingSdl
? buildSortedSchemaFromSchemaObject(
this.helper.createSchemaObject({
sdl: args.existingSdl,
}),
)
: null;
incomingSchema = args.incomingSdl
? buildSortedSchemaFromSchemaObject(
this.helper.createSchemaObject({
sdl: args.incomingSdl,
}),
)
: null;
} catch (error) {
this.logger.error('Failed to build schema for diff. Skip diff check.');
return { return {
status: 'skipped', status: 'skipped',
} satisfies CheckResult; } satisfies CheckResult;
} }
let existingSchema: GraphQLSchema; if (existingSchema === null || incomingSchema === null) {
let incomingSchema: GraphQLSchema; this.logger.debug('Skip diff check due to either existing or incoming SDL being absent.');
try {
existingSchema = buildSortedSchemaFromSchemaObject(
this.helper.createSchemaObject({
sdl: args.existingSdl,
}),
);
incomingSchema = buildSortedSchemaFromSchemaObject(
this.helper.createSchemaObject({
sdl: args.incomingSdl,
}),
);
} catch (error) {
this.logger.error('Failed to build schema for diff. Skip diff check.');
return { return {
status: 'skipped', status: 'skipped',
data: {
coordinatesDiff: incomingSchema ? diffSchemaCoordinates(null, incomingSchema) : null,
},
} satisfies CheckResult; } satisfies CheckResult;
} }
@ -511,6 +521,8 @@ export class RegistryChecks {
const safeChanges: Array<SchemaChangeType> = []; const safeChanges: Array<SchemaChangeType> = [];
const breakingChanges: Array<SchemaChangeType> = []; const breakingChanges: Array<SchemaChangeType> = [];
const coordinatesDiff = diffSchemaCoordinates(existingSchema, incomingSchema);
for (const change of inspectorChanges) { for (const change of inspectorChanges) {
if (change.criticality === CriticalityLevel.Breaking) { if (change.criticality === CriticalityLevel.Breaking) {
if (change.isSafeBasedOnUsage === true) { if (change.isSafeBasedOnUsage === true) {
@ -549,6 +561,7 @@ export class RegistryChecks {
} }
return null; return null;
}, },
coordinatesDiff,
}, },
} satisfies SchemaDiffFailure; } satisfies SchemaDiffFailure;
} }
@ -568,6 +581,7 @@ export class RegistryChecks {
} }
return null; return null;
}, },
coordinatesDiff,
}, },
} satisfies SchemaDiffSuccess; } satisfies SchemaDiffSuccess;
} }

View file

@ -40,6 +40,7 @@ import { TargetManager } from '../../target/providers/target-manager';
import { BreakingSchemaChangeUsageHelper } from './breaking-schema-changes-helper'; import { BreakingSchemaChangeUsageHelper } from './breaking-schema-changes-helper';
import { SCHEMA_MODULE_CONFIG, type SchemaModuleConfig } from './config'; import { SCHEMA_MODULE_CONFIG, type SchemaModuleConfig } from './config';
import { Contracts } from './contracts'; import { Contracts } from './contracts';
import type { SchemaCoordinatesDiffResult } from './inspector';
import { FederationOrchestrator } from './orchestrators/federation'; import { FederationOrchestrator } from './orchestrators/federation';
import { SingleOrchestrator } from './orchestrators/single'; import { SingleOrchestrator } from './orchestrators/single';
import { StitchingOrchestrator } from './orchestrators/stitching'; import { StitchingOrchestrator } from './orchestrators/stitching';
@ -427,6 +428,7 @@ export class SchemaManager {
projectType: ProjectType; projectType: ProjectType;
actionFn(): Promise<void>; actionFn(): Promise<void>;
changes: Array<SchemaChangeType>; changes: Array<SchemaChangeType>;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
previousSchemaVersion: string | null; previousSchemaVersion: string | null;
diffSchemaVersionId: string | null; diffSchemaVersionId: string | null;
github: null | { github: null | {
@ -460,13 +462,18 @@ export class SchemaManager {
) { ) {
this.logger.info( this.logger.info(
'Creating a new version (input=%o)', 'Creating a new version (input=%o)',
lodash.omit(input, [ lodash.pick(input, [
'schema', 'commit',
'actionFn', 'author',
'changes', 'valid',
'compositeSchemaSDL', 'service',
'supergraphSDL', 'logIds',
'schemaCompositionErrors', 'url',
'projectType',
'previousSchemaVersion',
'diffSchemaVersionId',
'github',
'conditionalBreakingChangeMetadata',
]), ]),
); );

View file

@ -1028,7 +1028,14 @@ export class SchemaPublisher {
); );
const token = this.authManager.ensureApiToken(); const token = this.authManager.ensureApiToken();
const contracts = await this.contracts.getActiveContractsByTargetId({ targetId: input.target }); const [contracts, latestVersion] = await Promise.all([
this.contracts.getActiveContractsByTargetId({ targetId: input.target }),
this.schemaManager.getMaybeLatestVersion({
organization: input.organization,
project: input.project,
target: input.target,
}),
]);
const checksum = createHash('md5') const checksum = createHash('md5')
.update( .update(
@ -1042,6 +1049,10 @@ export class SchemaPublisher {
contractId: contract.id, contractId: contract.id,
contractName: contract.contractName, contractName: contract.contractName,
})), })),
// We include the latest version ID to avoid caching a schema publication that targets different versions.
// When deleting a schema, and publishing it again, the latest version ID will be different.
// If we don't include it, the cache will return the previous result.
latestVersionId: latestVersion?.id,
}), }),
) )
.update(token) .update(token)
@ -1335,6 +1346,7 @@ export class SchemaPublisher {
composable: deleteResult.state.composable, composable: deleteResult.state.composable,
diffSchemaVersionId, diffSchemaVersionId,
changes: deleteResult.state.changes, changes: deleteResult.state.changes,
coordinatesDiff: deleteResult.state.coordinatesDiff,
contracts: contracts:
deleteResult.state.contracts?.map(contract => ({ deleteResult.state.contracts?.map(contract => ({
contractId: contract.contractId, contractId: contract.contractId,
@ -1909,6 +1921,7 @@ export class SchemaPublisher {
} }
}, },
changes, changes,
coordinatesDiff: publishResult.state.coordinatesDiff,
diffSchemaVersionId, diffSchemaVersionId,
previousSchemaVersion: latestVersion?.version ?? null, previousSchemaVersion: latestVersion?.version ?? null,
conditionalBreakingChangeMetadata: await this.getConditionalBreakingChangeMetadata({ conditionalBreakingChangeMetadata: await this.getConditionalBreakingChangeMetadata({

View file

@ -44,6 +44,7 @@ import type { OrganizationAccessScope } from '../../auth/providers/organization-
import type { ProjectAccessScope } from '../../auth/providers/project-access'; import type { ProjectAccessScope } from '../../auth/providers/project-access';
import type { TargetAccessScope } from '../../auth/providers/target-access'; import type { TargetAccessScope } from '../../auth/providers/target-access';
import type { Contracts } from '../../schema/providers/contracts'; import type { Contracts } from '../../schema/providers/contracts';
import type { SchemaCoordinatesDiffResult } from '../../schema/providers/inspector';
export interface OrganizationSelector { export interface OrganizationSelector {
organization: string; organization: string;
@ -442,6 +443,7 @@ export interface Storage {
diffSchemaVersionId: string | null; diffSchemaVersionId: string | null;
conditionalBreakingChangeMetadata: null | ConditionalBreakingChangeMetadata; conditionalBreakingChangeMetadata: null | ConditionalBreakingChangeMetadata;
contracts: null | Array<CreateContractVersionInput>; contracts: null | Array<CreateContractVersionInput>;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
} & TargetSelector & } & TargetSelector &
( (
| { | {
@ -480,6 +482,7 @@ export interface Storage {
}; };
contracts: null | Array<CreateContractVersionInput>; contracts: null | Array<CreateContractVersionInput>;
conditionalBreakingChangeMetadata: null | ConditionalBreakingChangeMetadata; conditionalBreakingChangeMetadata: null | ConditionalBreakingChangeMetadata;
coordinatesDiff: SchemaCoordinatesDiffResult | null;
} & TargetSelector) & } & TargetSelector) &
( (
| { | {

View file

@ -16,7 +16,7 @@
"itty-router": "4.2.2", "itty-router": "4.2.2",
"toucan-js": "3.4.0", "toucan-js": "3.4.0",
"undici": "6.19.2", "undici": "6.19.2",
"vitest": "1.6.0", "vitest": "2.0.3",
"workers-loki-logger": "0.1.15", "workers-loki-logger": "0.1.15",
"zod": "3.23.8" "zod": "3.23.8"
} }

View file

@ -271,6 +271,15 @@ export interface schema_checks {
updated_at: Date; updated_at: Date;
} }
export interface schema_coordinate_status {
coordinate: string;
created_at: Date;
created_in_version_id: string;
deprecated_at: Date | null;
deprecated_in_version_id: string | null;
target_id: string;
}
export interface schema_log { export interface schema_log {
action: string; action: string;
author: string; author: string;
@ -418,6 +427,7 @@ export interface DBTables {
projects: projects; projects: projects;
schema_change_approvals: schema_change_approvals; schema_change_approvals: schema_change_approvals;
schema_checks: schema_checks; schema_checks: schema_checks;
schema_coordinate_status: schema_coordinate_status;
schema_log: schema_log; schema_log: schema_log;
schema_policy_config: schema_policy_config; schema_policy_config: schema_policy_config;
schema_version_changes: schema_version_changes; schema_version_changes: schema_version_changes;

View file

@ -25,6 +25,7 @@ import type {
} from '@hive/api'; } from '@hive/api';
import { context, SpanKind, SpanStatusCode, trace } from '@hive/service-common'; import { context, SpanKind, SpanStatusCode, trace } from '@hive/service-common';
import { batch } from '@theguild/buddy'; import { batch } from '@theguild/buddy';
import type { SchemaCoordinatesDiffResult } from '../../api/src/modules/schema/providers/inspector';
import { import {
createSDLHash, createSDLHash,
OrganizationMemberRoleModel, OrganizationMemberRoleModel,
@ -2644,6 +2645,14 @@ export async function createStorage(
}); });
} }
if (args.coordinatesDiff) {
await updateSchemaCoordinateStatus(trx, {
targetId: args.target,
versionId: newVersion.id,
coordinatesDiff: args.coordinatesDiff,
});
}
for (const contract of args.contracts ?? []) { for (const contract of args.contracts ?? []) {
const schemaVersionContractId = await insertSchemaVersionContract(trx, { const schemaVersionContractId = await insertSchemaVersionContract(trx, {
schemaVersionId: newVersion.id, schemaVersionId: newVersion.id,
@ -2756,6 +2765,14 @@ export async function createStorage(
}); });
} }
if (input.coordinatesDiff) {
await updateSchemaCoordinateStatus(trx, {
targetId: input.target,
versionId: version.id,
coordinatesDiff: input.coordinatesDiff,
});
}
await input.actionFn(); await input.actionFn();
return { return {
@ -5137,6 +5154,80 @@ async function insertSchemaVersionContract(
return zod.string().parse(id); return zod.string().parse(id);
} }
async function updateSchemaCoordinateStatus(
trx: DatabaseTransactionConnection,
args: {
targetId: string;
versionId: string;
coordinatesDiff: SchemaCoordinatesDiffResult;
},
) {
const actions: Promise<unknown>[] = [];
if (args.coordinatesDiff.deleted) {
actions.push(
trx.query(sql`/* schema_coordinate_status_deleted */
DELETE FROM schema_coordinate_status
WHERE
target_id = ${args.targetId}
AND
coordinate = ANY(${sql.array(Array.from(args.coordinatesDiff.deleted), 'text')})
AND
created_at <= NOW()
`),
);
}
if (args.coordinatesDiff.added) {
actions.push(
trx.query(sql`/* schema_coordinate_status_inserted */
INSERT INTO schema_coordinate_status
( target_id, coordinate, created_in_version_id, deprecated_at, deprecated_in_version_id )
SELECT * FROM ${sql.unnest(
Array.from(args.coordinatesDiff.added).map(coordinate => {
const isDeprecatedAsWell = args.coordinatesDiff.deprecated.has(coordinate);
return [
args.targetId,
coordinate,
args.versionId,
// if it's added and deprecated at the same time
isDeprecatedAsWell ? 'NOW()' : null,
isDeprecatedAsWell ? args.versionId : null,
];
}),
['uuid', 'text', 'uuid', 'date', 'uuid'],
)}
`),
);
}
if (args.coordinatesDiff.undeprecated) {
actions.push(
trx.query(sql`/* schema_coordinate_status_undeprecated */
UPDATE schema_coordinate_status
SET deprecated_at = NULL, deprecated_in_version_id = NULL
WHERE
target_id = ${args.targetId}
AND
coordinate = ANY(${sql.array(Array.from(args.coordinatesDiff.undeprecated), 'text')})
`),
);
}
await Promise.all(actions);
if (args.coordinatesDiff.deprecated) {
await trx.query(sql`/* schema_coordinate_status_deprecated */
UPDATE schema_coordinate_status
SET deprecated_at = NOW(), deprecated_in_version_id = ${args.versionId}
WHERE
target_id = ${args.targetId}
AND
coordinate = ANY(${sql.array(Array.from(args.coordinatesDiff.deprecated), 'text')})
`);
}
}
/** /**
* Small helper utility for jsonifying a nullable object. * Small helper utility for jsonifying a nullable object.
*/ */

View file

@ -4,7 +4,7 @@ import {
DiffEditor as MonacoDiffEditor, DiffEditor as MonacoDiffEditor,
Editor as MonacoEditor, Editor as MonacoEditor,
} from '@monaco-editor/react'; } from '@monaco-editor/react';
import pkg from '../../package.json' assert { type: 'json' }; import pkg from '../../package.json' with { type: 'json' };
loader.config({ loader.config({
paths: { paths: {

View file

@ -159,7 +159,7 @@ importers:
version: 0.18.1(prettier@3.3.3) version: 0.18.1(prettier@3.3.3)
prettier-plugin-tailwindcss: prettier-plugin-tailwindcss:
specifier: 0.6.5 specifier: 0.6.5
version: 0.6.5(@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3))(prettier@3.3.3) version: 0.6.5(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3)
pretty-quick: pretty-quick:
specifier: 4.0.0 specifier: 4.0.0
version: 4.0.0(prettier@3.3.3) version: 4.0.0(prettier@3.3.3)
@ -185,8 +185,8 @@ importers:
specifier: 4.3.2 specifier: 4.3.2
version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)) version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
deployment: deployment:
dependencies: dependencies:
@ -333,8 +333,8 @@ importers:
specifier: 2.6.3 specifier: 2.6.3
version: 2.6.3 version: 2.6.3
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
zod: zod:
specifier: 3.23.8 specifier: 3.23.8
version: 3.23.8 version: 3.23.8
@ -367,8 +367,8 @@ importers:
specifier: 14.0.0-beta.7 specifier: 14.0.0-beta.7
version: 14.0.0-beta.7 version: 14.0.0-beta.7
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
ws: ws:
specifier: 8.18.0 specifier: 8.18.0
version: 8.18.0 version: 8.18.0
@ -496,8 +496,8 @@ importers:
specifier: 2.6.3 specifier: 2.6.3
version: 2.6.3 version: 2.6.3
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
publishDirectory: dist publishDirectory: dist
packages/libraries/envelop: packages/libraries/envelop:
@ -573,8 +573,8 @@ importers:
specifier: 14.0.0-beta.7 specifier: 14.0.0-beta.7
version: 14.0.0-beta.7 version: 14.0.0-beta.7
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
ws: ws:
specifier: 8.18.0 specifier: 8.18.0
version: 8.18.0 version: 8.18.0
@ -612,6 +612,9 @@ importers:
got: got:
specifier: 14.4.1 specifier: 14.4.1
version: 14.4.1(patch_hash=b6pwqmrs3qqykctltsasvrfwti) version: 14.4.1(patch_hash=b6pwqmrs3qqykctltsasvrfwti)
graphql:
specifier: 16.9.0
version: 16.9.0
p-limit: p-limit:
specifier: 4.0.0 specifier: 4.0.0
version: 4.0.0 version: 4.0.0
@ -800,8 +803,8 @@ importers:
specifier: 2.6.3 specifier: 2.6.3
version: 2.6.3 version: 2.6.3
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
zod: zod:
specifier: 3.23.8 specifier: 3.23.8
version: 3.23.8 version: 3.23.8
@ -833,8 +836,8 @@ importers:
specifier: 6.19.2 specifier: 6.19.2
version: 6.19.2 version: 6.19.2
vitest: vitest:
specifier: 1.6.0 specifier: 2.0.3
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) version: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
workers-loki-logger: workers-loki-logger:
specifier: 0.1.15 specifier: 0.1.15
version: 0.1.15 version: 0.1.15
@ -1744,7 +1747,7 @@ importers:
version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))) version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
'@storybook/addon-interactions': '@storybook/addon-interactions':
specifier: 8.2.2 specifier: 8.2.2
version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)) version: 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
'@storybook/addon-links': '@storybook/addon-links':
specifier: 8.2.2 specifier: 8.2.2
version: 8.2.2(react@18.3.1)(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))) version: 8.2.2(react@18.3.1)(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
@ -4021,6 +4024,7 @@ packages:
'@fastify/vite@6.0.7': '@fastify/vite@6.0.7':
resolution: {integrity: sha512-+dRo9KUkvmbqdmBskG02SwigWl06Mwkw8SBDK1zTNH6vd4DyXbRvI7RmJEmBkLouSU81KTzy1+OzwHSffqSD6w==} resolution: {integrity: sha512-+dRo9KUkvmbqdmBskG02SwigWl06Mwkw8SBDK1zTNH6vd4DyXbRvI7RmJEmBkLouSU81KTzy1+OzwHSffqSD6w==}
bundledDependencies: []
'@floating-ui/core@1.2.6': '@floating-ui/core@1.2.6':
resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==} resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==}
@ -4735,6 +4739,15 @@ packages:
'@vue/compiler-sfc': '@vue/compiler-sfc':
optional: true optional: true
'@ianvs/prettier-plugin-sort-imports@4.3.1':
resolution: {integrity: sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==}
peerDependencies:
'@vue/compiler-sfc': 2.7.x || 3.x
prettier: 2 || 3
peerDependenciesMeta:
'@vue/compiler-sfc':
optional: true
'@inquirer/confirm@3.1.8': '@inquirer/confirm@3.1.8':
resolution: {integrity: sha512-f3INZ+ca4dQdn+MQiq1yP/mOIR/Oc8BLRYuDh6ciToWd6z4W8yArfzjBCMQ0BPY8PcJKwZxGIt8Z6yNT32eSTw==} resolution: {integrity: sha512-f3INZ+ca4dQdn+MQiq1yP/mOIR/Oc8BLRYuDh6ciToWd6z4W8yArfzjBCMQ0BPY8PcJKwZxGIt8Z6yNT32eSTw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -4820,10 +4833,6 @@ packages:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
'@jridgewell/set-array@1.1.2':
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
'@jridgewell/set-array@1.2.1': '@jridgewell/set-array@1.2.1':
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -8150,18 +8159,30 @@ packages:
'@vitest/expect@1.6.0': '@vitest/expect@1.6.0':
resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
'@vitest/runner@1.6.0': '@vitest/expect@2.0.3':
resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} resolution: {integrity: sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==}
'@vitest/snapshot@1.6.0': '@vitest/pretty-format@2.0.3':
resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} resolution: {integrity: sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==}
'@vitest/runner@2.0.3':
resolution: {integrity: sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==}
'@vitest/snapshot@2.0.3':
resolution: {integrity: sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==}
'@vitest/spy@1.6.0': '@vitest/spy@1.6.0':
resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
'@vitest/spy@2.0.3':
resolution: {integrity: sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==}
'@vitest/utils@1.6.0': '@vitest/utils@1.6.0':
resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
'@vitest/utils@2.0.3':
resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==}
'@webassemblyjs/ast@1.12.1': '@webassemblyjs/ast@1.12.1':
resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
@ -8518,6 +8539,10 @@ packages:
assertion-error@1.1.0: assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
ast-types-flow@0.0.8: ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
@ -8880,6 +8905,10 @@ packages:
resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
engines: {node: '>=4'} engines: {node: '>=4'}
chai@5.1.1:
resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
engines: {node: '>=12'}
chalk@2.3.0: chalk@2.3.0:
resolution: {integrity: sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==} resolution: {integrity: sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -8936,6 +8965,10 @@ packages:
check-error@1.0.3: check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
check-more-types@2.24.0: check-more-types@2.24.0:
resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -9628,6 +9661,10 @@ packages:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
engines: {node: '>=6'} engines: {node: '>=6'}
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deep-equal@2.1.0: deep-equal@2.1.0:
resolution: {integrity: sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==} resolution: {integrity: sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==}
@ -11848,9 +11885,6 @@ packages:
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-tokens@8.0.3:
resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==}
js-yaml@3.14.1: js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true hasBin: true
@ -11953,9 +11987,6 @@ packages:
resolution: {integrity: sha512-qCRJWlbP2v6HbmKW7R3lFbeiVWHo+oMJ0j+MizwvauqnCV/EvtAeEeuCgoc/ErtsuoKgYB8U4Ih8AxJbXoE6/g==} resolution: {integrity: sha512-qCRJWlbP2v6HbmKW7R3lFbeiVWHo+oMJ0j+MizwvauqnCV/EvtAeEeuCgoc/ErtsuoKgYB8U4Ih8AxJbXoE6/g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
jsonc-parser@3.2.0:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
jsonfile@4.0.0: jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
@ -12159,10 +12190,6 @@ packages:
resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==} resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
localforage@1.10.0: localforage@1.10.0:
resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
@ -12262,6 +12289,9 @@ packages:
loupe@2.3.7: loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
loupe@3.1.1:
resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
lower-case-first@2.0.2: lower-case-first@2.0.2:
resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==} resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==}
@ -12321,6 +12351,9 @@ packages:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'} engines: {node: '>=12'}
magic-string@0.30.10:
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
magic-string@0.30.5: magic-string@0.30.5:
resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -12940,9 +12973,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
mlly@1.4.2:
resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
mnemonist@0.39.6: mnemonist@0.39.6:
resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
@ -13419,10 +13449,6 @@ packages:
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
p-limit@5.0.0:
resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
engines: {node: '>=18'}
p-limit@6.1.0: p-limit@6.1.0:
resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==} resolution: {integrity: sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -13626,9 +13652,16 @@ packages:
pathe@1.1.1: pathe@1.1.1:
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
pathval@1.1.1: pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
pathval@2.0.0:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
pend@1.2.0: pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
@ -13768,9 +13801,6 @@ packages:
resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
pkg-types@1.0.3:
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
pluralize@8.0.0: pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -15228,8 +15258,8 @@ packages:
std-env@3.3.3: std-env@3.3.3:
resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
std-env@3.6.0: std-env@3.7.0:
resolution: {integrity: sha512-aFZ19IgVmhdB2uX599ve2kE6BIE3YMnQ6Gp6BURhW/oIzpXGKr878TQfAQZn1+i0Flcc/UKUy1gOlcfaUBCryg==} resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
storybook@8.2.2: storybook@8.2.2:
resolution: {integrity: sha512-xDT9gyzAEFQNeK7P+Mj/8bNzN+fbm6/4D6ihdSzmczayjydpNjMs74HDHMY6S4Bfu6tRVyEK2ALPGnr6ZVofBA==} resolution: {integrity: sha512-xDT9gyzAEFQNeK7P+Mj/8bNzN+fbm6/4D6ihdSzmczayjydpNjMs74HDHMY6S4Bfu6tRVyEK2ALPGnr6ZVofBA==}
@ -15338,9 +15368,6 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
strip-literal@2.0.0:
resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==}
stripe@14.25.0: stripe@14.25.0:
resolution: {integrity: sha512-wQS3GNMofCXwH8TSje8E1SE8zr6ODiGtHQgPtO95p9Mb4FhKC9jvXR2NUTpZ9ZINlckJcFidCmaTFV4P6vsb9g==} resolution: {integrity: sha512-wQS3GNMofCXwH8TSje8E1SE8zr6ODiGtHQgPtO95p9Mb4FhKC9jvXR2NUTpZ9ZINlckJcFidCmaTFV4P6vsb9g==}
engines: {node: '>=12.*'} engines: {node: '>=12.*'}
@ -15580,17 +15607,25 @@ packages:
tiny-warning@1.0.3: tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
tinybench@2.5.1: tinybench@2.8.0:
resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==}
tinypool@0.8.3: tinypool@1.0.0:
resolution: {integrity: sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==} resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==}
engines: {node: ^18.0.0 || >=20.0.0}
tinyrainbow@1.2.0:
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
tinyspy@2.2.0: tinyspy@2.2.0:
resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
tinyspy@3.0.0:
resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==}
engines: {node: '>=14.0.0'}
title-case@3.0.3: title-case@3.0.3:
resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==}
@ -15901,9 +15936,6 @@ packages:
uc.micro@2.1.0: uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
ufo@1.3.0:
resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}
uglify-js@3.17.4: uglify-js@3.17.4:
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -16225,8 +16257,8 @@ packages:
vfile@6.0.1: vfile@6.0.1:
resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
vite-node@1.6.0: vite-node@2.0.3:
resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} resolution: {integrity: sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
@ -16266,15 +16298,15 @@ packages:
terser: terser:
optional: true optional: true
vitest@1.6.0: vitest@2.0.3:
resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} resolution: {integrity: sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@edge-runtime/vm': '*' '@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || >=20.0.0 '@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': 1.6.0 '@vitest/browser': 2.0.3
'@vitest/ui': 1.6.0 '@vitest/ui': 2.0.3
happy-dom: '*' happy-dom: '*'
jsdom: '*' jsdom: '*'
peerDependenciesMeta: peerDependenciesMeta:
@ -18015,19 +18047,19 @@ snapshots:
'@babel/helper-module-transforms@7.23.3(@babel/core@7.22.9)': '@babel/helper-module-transforms@7.23.3(@babel/core@7.22.9)':
dependencies: dependencies:
'@babel/core': 7.22.9 '@babel/core': 7.22.9
'@babel/helper-environment-visitor': 7.22.20 '@babel/helper-environment-visitor': 7.24.7
'@babel/helper-module-imports': 7.22.15 '@babel/helper-module-imports': 7.22.15
'@babel/helper-simple-access': 7.22.5 '@babel/helper-simple-access': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.24.7
'@babel/helper-validator-identifier': 7.24.7 '@babel/helper-validator-identifier': 7.24.7
'@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0)': '@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0)':
dependencies: dependencies:
'@babel/core': 7.24.0 '@babel/core': 7.24.0
'@babel/helper-environment-visitor': 7.22.20 '@babel/helper-environment-visitor': 7.24.7
'@babel/helper-module-imports': 7.22.15 '@babel/helper-module-imports': 7.22.15
'@babel/helper-simple-access': 7.22.5 '@babel/helper-simple-access': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-split-export-declaration': 7.24.7
'@babel/helper-validator-identifier': 7.24.7 '@babel/helper-validator-identifier': 7.24.7
'@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)': '@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)':
@ -18821,7 +18853,7 @@ snapshots:
'@babel/template@7.24.0': '@babel/template@7.24.0':
dependencies: dependencies:
'@babel/code-frame': 7.24.2 '@babel/code-frame': 7.24.2
'@babel/parser': 7.24.5 '@babel/parser': 7.24.7
'@babel/types': 7.24.7 '@babel/types': 7.24.7
'@babel/template@7.24.7': '@babel/template@7.24.7':
@ -18848,12 +18880,12 @@ snapshots:
'@babel/traverse@7.24.5': '@babel/traverse@7.24.5':
dependencies: dependencies:
'@babel/code-frame': 7.24.2 '@babel/code-frame': 7.24.2
'@babel/generator': 7.24.5 '@babel/generator': 7.24.7
'@babel/helper-environment-visitor': 7.22.20 '@babel/helper-environment-visitor': 7.22.20
'@babel/helper-function-name': 7.23.0 '@babel/helper-function-name': 7.23.0
'@babel/helper-hoist-variables': 7.22.5 '@babel/helper-hoist-variables': 7.22.5
'@babel/helper-split-export-declaration': 7.24.5 '@babel/helper-split-export-declaration': 7.24.5
'@babel/parser': 7.24.5 '@babel/parser': 7.24.7
'@babel/types': 7.24.7 '@babel/types': 7.24.7
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
globals: 11.12.0 globals: 11.12.0
@ -20933,15 +20965,28 @@ snapshots:
'@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3)': '@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3)':
dependencies: dependencies:
'@babel/core': 7.24.7 '@babel/core': 7.24.7
'@babel/generator': 7.23.6 '@babel/generator': 7.24.7
'@babel/parser': 7.24.0 '@babel/parser': 7.24.7
'@babel/traverse': 7.24.0 '@babel/traverse': 7.24.7
'@babel/types': 7.24.7 '@babel/types': 7.24.7
prettier: 3.3.3 prettier: 3.3.3
semver: 7.6.2 semver: 7.6.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)':
dependencies:
'@babel/core': 7.24.7
'@babel/generator': 7.24.7
'@babel/parser': 7.24.7
'@babel/traverse': 7.24.7
'@babel/types': 7.24.7
prettier: 3.3.3
semver: 7.6.2
transitivePeerDependencies:
- supports-color
optional: true
'@inquirer/confirm@3.1.8': '@inquirer/confirm@3.1.8':
dependencies: dependencies:
'@inquirer/core': 8.2.1 '@inquirer/core': 8.2.1
@ -21046,9 +21091,9 @@ snapshots:
'@jridgewell/gen-mapping@0.3.3': '@jridgewell/gen-mapping@0.3.3':
dependencies: dependencies:
'@jridgewell/set-array': 1.1.2 '@jridgewell/set-array': 1.2.1
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.20 '@jridgewell/trace-mapping': 0.3.25
'@jridgewell/gen-mapping@0.3.5': '@jridgewell/gen-mapping@0.3.5':
dependencies: dependencies:
@ -21058,8 +21103,6 @@ snapshots:
'@jridgewell/resolve-uri@3.1.1': {} '@jridgewell/resolve-uri@3.1.1': {}
'@jridgewell/set-array@1.1.2': {}
'@jridgewell/set-array@1.2.1': {} '@jridgewell/set-array@1.2.1': {}
'@jridgewell/source-map@0.3.6': '@jridgewell/source-map@0.3.6':
@ -24213,11 +24256,11 @@ snapshots:
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
storybook: 8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)) storybook: 8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))
'@storybook/addon-interactions@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))': '@storybook/addon-interactions@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
dependencies: dependencies:
'@storybook/global': 5.0.0 '@storybook/global': 5.0.0
'@storybook/instrumenter': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))) '@storybook/instrumenter': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
'@storybook/test': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)) '@storybook/test': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
polished: 4.2.2 polished: 4.2.2
storybook: 8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)) storybook: 8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))
ts-dedent: 2.2.0 ts-dedent: 2.2.0
@ -24411,12 +24454,12 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.3 typescript: 5.5.3
'@storybook/test@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))': '@storybook/test@8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
dependencies: dependencies:
'@storybook/csf': 0.1.11 '@storybook/csf': 0.1.11
'@storybook/instrumenter': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7))) '@storybook/instrumenter': 8.2.2(storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)))
'@testing-library/dom': 10.1.0 '@testing-library/dom': 10.1.0
'@testing-library/jest-dom': 6.4.5(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)) '@testing-library/jest-dom': 6.4.5(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))
'@testing-library/user-event': 14.5.2(@testing-library/dom@10.1.0) '@testing-library/user-event': 14.5.2(@testing-library/dom@10.1.0)
'@vitest/expect': 1.6.0 '@vitest/expect': 1.6.0
'@vitest/spy': 1.6.0 '@vitest/spy': 1.6.0
@ -24581,7 +24624,7 @@ snapshots:
lz-string: 1.5.0 lz-string: 1.5.0
pretty-format: 27.5.1 pretty-format: 27.5.1
'@testing-library/jest-dom@6.4.5(vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))': '@testing-library/jest-dom@6.4.5(vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1))':
dependencies: dependencies:
'@adobe/css-tools': 4.3.3 '@adobe/css-tools': 4.3.3
'@babel/runtime': 7.24.7 '@babel/runtime': 7.24.7
@ -24592,7 +24635,7 @@ snapshots:
lodash: 4.17.21 lodash: 4.17.21
redent: 3.0.0 redent: 3.0.0
optionalDependencies: optionalDependencies:
vitest: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vitest: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
'@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0)': '@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0)':
dependencies: dependencies:
@ -24655,8 +24698,8 @@ snapshots:
'@typescript-eslint/parser': 7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3) '@typescript-eslint/parser': 7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3)
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva) eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
eslint-config-prettier: 9.1.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-config-prettier: 9.1.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-plugin-jsonc: 2.11.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-plugin-jsonc: 2.11.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-plugin-mdx: 3.0.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-plugin-mdx: 3.0.0(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
@ -24776,7 +24819,7 @@ snapshots:
'@types/babel__template@7.4.1': '@types/babel__template@7.4.1':
dependencies: dependencies:
'@babel/parser': 7.24.5 '@babel/parser': 7.24.7
'@babel/types': 7.24.7 '@babel/types': 7.24.7
'@types/babel__traverse@7.18.3': '@types/babel__traverse@7.18.3':
@ -25300,22 +25343,36 @@ snapshots:
'@vitest/utils': 1.6.0 '@vitest/utils': 1.6.0
chai: 4.4.1 chai: 4.4.1
'@vitest/runner@1.6.0': '@vitest/expect@2.0.3':
dependencies: dependencies:
'@vitest/utils': 1.6.0 '@vitest/spy': 2.0.3
p-limit: 5.0.0 '@vitest/utils': 2.0.3
pathe: 1.1.1 chai: 5.1.1
tinyrainbow: 1.2.0
'@vitest/snapshot@1.6.0': '@vitest/pretty-format@2.0.3':
dependencies: dependencies:
magic-string: 0.30.5 tinyrainbow: 1.2.0
pathe: 1.1.1
pretty-format: 29.7.0 '@vitest/runner@2.0.3':
dependencies:
'@vitest/utils': 2.0.3
pathe: 1.1.2
'@vitest/snapshot@2.0.3':
dependencies:
'@vitest/pretty-format': 2.0.3
magic-string: 0.30.10
pathe: 1.1.2
'@vitest/spy@1.6.0': '@vitest/spy@1.6.0':
dependencies: dependencies:
tinyspy: 2.2.0 tinyspy: 2.2.0
'@vitest/spy@2.0.3':
dependencies:
tinyspy: 3.0.0
'@vitest/utils@1.6.0': '@vitest/utils@1.6.0':
dependencies: dependencies:
diff-sequences: 29.6.3 diff-sequences: 29.6.3
@ -25323,6 +25380,13 @@ snapshots:
loupe: 2.3.7 loupe: 2.3.7
pretty-format: 29.7.0 pretty-format: 29.7.0
'@vitest/utils@2.0.3':
dependencies:
'@vitest/pretty-format': 2.0.3
estree-walker: 3.0.3
loupe: 3.1.1
tinyrainbow: 1.2.0
'@webassemblyjs/ast@1.12.1': '@webassemblyjs/ast@1.12.1':
dependencies: dependencies:
'@webassemblyjs/helper-numbers': 1.11.6 '@webassemblyjs/helper-numbers': 1.11.6
@ -25716,6 +25780,8 @@ snapshots:
assertion-error@1.1.0: {} assertion-error@1.1.0: {}
assertion-error@2.0.1: {}
ast-types-flow@0.0.8: {} ast-types-flow@0.0.8: {}
ast-types@0.16.1: ast-types@0.16.1:
@ -26187,6 +26253,14 @@ snapshots:
pathval: 1.1.1 pathval: 1.1.1
type-detect: 4.0.8 type-detect: 4.0.8
chai@5.1.1:
dependencies:
assertion-error: 2.0.1
check-error: 2.1.1
deep-eql: 5.0.2
loupe: 3.1.1
pathval: 2.0.0
chalk@2.3.0: chalk@2.3.0:
dependencies: dependencies:
ansi-styles: 3.2.1 ansi-styles: 3.2.1
@ -26261,6 +26335,8 @@ snapshots:
dependencies: dependencies:
get-func-name: 2.0.2 get-func-name: 2.0.2
check-error@2.1.1: {}
check-more-types@2.24.0: {} check-more-types@2.24.0: {}
cheerio-select@1.6.0: cheerio-select@1.6.0:
@ -27037,6 +27113,8 @@ snapshots:
dependencies: dependencies:
type-detect: 4.0.8 type-detect: 4.0.8
deep-eql@5.0.2: {}
deep-equal@2.1.0: deep-equal@2.1.0:
dependencies: dependencies:
call-bind: 1.0.5 call-bind: 1.0.5
@ -27564,13 +27642,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)): eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
dependencies: dependencies:
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
enhanced-resolve: 5.17.0 enhanced-resolve: 5.17.0
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva) eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
fast-glob: 3.3.2 fast-glob: 3.3.2
get-tsconfig: 4.7.5 get-tsconfig: 4.7.5
is-core-module: 2.13.1 is-core-module: 2.13.1
@ -27601,14 +27679,14 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)): eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
dependencies: dependencies:
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7(supports-color@8.1.1)
optionalDependencies: optionalDependencies:
'@typescript-eslint/parser': 7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3) '@typescript-eslint/parser': 7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3)
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva) eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -27624,7 +27702,7 @@ snapshots:
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva) eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
eslint-compat-utils: 0.1.2(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-compat-utils: 0.1.2(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)): eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)):
dependencies: dependencies:
array-includes: 3.1.7 array-includes: 3.1.7
array.prototype.findlastindex: 1.2.3 array.prototype.findlastindex: 1.2.3
@ -27634,7 +27712,7 @@ snapshots:
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva) eslint: 8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)) eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.1(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))(typescript@5.5.3))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva)))(eslint@8.57.0(patch_hash=fjbpfrtrjd6idngyeqxnwopfva))
hasown: 2.0.0 hasown: 2.0.0
is-core-module: 2.13.1 is-core-module: 2.13.1
is-glob: 4.0.3 is-glob: 4.0.3
@ -29803,8 +29881,6 @@ snapshots:
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-tokens@8.0.3: {}
js-yaml@3.14.1: js-yaml@3.14.1:
dependencies: dependencies:
argparse: 1.0.10 argparse: 1.0.10
@ -29923,8 +29999,6 @@ snapshots:
espree: 9.6.1 espree: 9.6.1
semver: 7.6.2 semver: 7.6.2
jsonc-parser@3.2.0: {}
jsonfile@4.0.0: jsonfile@4.0.0:
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -30171,11 +30245,6 @@ snapshots:
emojis-list: 3.0.0 emojis-list: 3.0.0
json5: 1.0.2 json5: 1.0.2
local-pkg@0.5.0:
dependencies:
mlly: 1.4.2
pkg-types: 1.0.3
localforage@1.10.0: localforage@1.10.0:
dependencies: dependencies:
lie: 3.1.1 lie: 3.1.1
@ -30265,6 +30334,10 @@ snapshots:
dependencies: dependencies:
get-func-name: 2.0.2 get-func-name: 2.0.2
loupe@3.1.1:
dependencies:
get-func-name: 2.0.2
lower-case-first@2.0.2: lower-case-first@2.0.2:
dependencies: dependencies:
tslib: 2.6.3 tslib: 2.6.3
@ -30314,6 +30387,10 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
magic-string@0.30.10:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
magic-string@0.30.5: magic-string@0.30.5:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
@ -31534,13 +31611,6 @@ snapshots:
mkdirp@3.0.1: {} mkdirp@3.0.1: {}
mlly@1.4.2:
dependencies:
acorn: 8.12.0
pathe: 1.1.1
pkg-types: 1.0.3
ufo: 1.3.0
mnemonist@0.39.6: mnemonist@0.39.6:
dependencies: dependencies:
obliterator: 2.0.4 obliterator: 2.0.4
@ -32106,10 +32176,6 @@ snapshots:
dependencies: dependencies:
yocto-queue: 1.0.0 yocto-queue: 1.0.0
p-limit@5.0.0:
dependencies:
yocto-queue: 1.0.0
p-limit@6.1.0: p-limit@6.1.0:
dependencies: dependencies:
yocto-queue: 1.1.1 yocto-queue: 1.1.1
@ -32346,8 +32412,12 @@ snapshots:
pathe@1.1.1: {} pathe@1.1.1: {}
pathe@1.1.2: {}
pathval@1.1.1: {} pathval@1.1.1: {}
pathval@2.0.0: {}
pend@1.2.0: {} pend@1.2.0: {}
performance-now@2.1.0: {} performance-now@2.1.0: {}
@ -32516,12 +32586,6 @@ snapshots:
dependencies: dependencies:
find-up: 6.3.0 find-up: 6.3.0
pkg-types@1.0.3:
dependencies:
jsonc-parser: 3.2.0
mlly: 1.4.2
pathe: 1.1.1
pluralize@8.0.0: {} pluralize@8.0.0: {}
polished@4.2.2: polished@4.2.2:
@ -32786,11 +32850,11 @@ snapshots:
sql-formatter: 15.0.2 sql-formatter: 15.0.2
tslib: 2.6.3 tslib: 2.6.3
prettier-plugin-tailwindcss@0.6.5(@ianvs/prettier-plugin-sort-imports@4.2.1(prettier@3.3.3))(prettier@3.3.3): prettier-plugin-tailwindcss@0.6.5(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier@3.3.3):
dependencies: dependencies:
prettier: 3.3.3 prettier: 3.3.3
optionalDependencies: optionalDependencies:
'@ianvs/prettier-plugin-sort-imports': 4.2.1(prettier@3.3.3) '@ianvs/prettier-plugin-sort-imports': 4.3.1(prettier@3.3.3)
prettier@2.8.8: {} prettier@2.8.8: {}
@ -34117,7 +34181,7 @@ snapshots:
std-env@3.3.3: {} std-env@3.3.3: {}
std-env@3.6.0: {} std-env@3.7.0: {}
storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)): storybook@8.2.2(@babel/preset-env@7.24.5(@babel/core@7.24.7)):
dependencies: dependencies:
@ -34270,10 +34334,6 @@ snapshots:
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
strip-literal@2.0.0:
dependencies:
js-tokens: 8.0.3
stripe@14.25.0: stripe@14.25.0:
dependencies: dependencies:
'@types/node': 20.14.10 '@types/node': 20.14.10
@ -34571,12 +34631,16 @@ snapshots:
tiny-warning@1.0.3: {} tiny-warning@1.0.3: {}
tinybench@2.5.1: {} tinybench@2.8.0: {}
tinypool@0.8.3: {} tinypool@1.0.0: {}
tinyrainbow@1.2.0: {}
tinyspy@2.2.0: {} tinyspy@2.2.0: {}
tinyspy@3.0.0: {}
title-case@3.0.3: title-case@3.0.3:
dependencies: dependencies:
tslib: 2.6.3 tslib: 2.6.3
@ -34870,8 +34934,6 @@ snapshots:
uc.micro@2.1.0: {} uc.micro@2.1.0: {}
ufo@1.3.0: {}
uglify-js@3.17.4: {} uglify-js@3.17.4: {}
unbox-primitive@1.0.2: unbox-primitive@1.0.2:
@ -35252,12 +35314,12 @@ snapshots:
unist-util-stringify-position: 4.0.0 unist-util-stringify-position: 4.0.0
vfile-message: 4.0.2 vfile-message: 4.0.2
vite-node@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1): vite-node@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
pathe: 1.1.1 pathe: 1.1.2
picocolors: 1.0.1 tinyrainbow: 1.2.0
vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
@ -35291,27 +35353,26 @@ snapshots:
less: 4.2.0 less: 4.2.0
terser: 5.31.1 terser: 5.31.1
vitest@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1): vitest@2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
dependencies: dependencies:
'@vitest/expect': 1.6.0 '@ampproject/remapping': 2.3.0
'@vitest/runner': 1.6.0 '@vitest/expect': 2.0.3
'@vitest/snapshot': 1.6.0 '@vitest/pretty-format': 2.0.3
'@vitest/spy': 1.6.0 '@vitest/runner': 2.0.3
'@vitest/utils': 1.6.0 '@vitest/snapshot': 2.0.3
acorn-walk: 8.3.2 '@vitest/spy': 2.0.3
chai: 4.4.1 '@vitest/utils': 2.0.3
chai: 5.1.1
debug: 4.3.5(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
execa: 8.0.1 execa: 8.0.1
local-pkg: 0.5.0 magic-string: 0.30.10
magic-string: 0.30.5 pathe: 1.1.2
pathe: 1.1.1 std-env: 3.7.0
picocolors: 1.0.1 tinybench: 2.8.0
std-env: 3.6.0 tinypool: 1.0.0
strip-literal: 2.0.0 tinyrainbow: 1.2.0
tinybench: 2.5.1
tinypool: 0.8.3
vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
vite-node: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vite-node: 2.0.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
why-is-node-running: 2.2.2 why-is-node-running: 2.2.2
optionalDependencies: optionalDependencies:
'@types/node': 20.14.10 '@types/node': 20.14.10

View file

@ -5,7 +5,12 @@ const { plugins, ...prettierConfig } = require('@theguild/prettier-config');
module.exports = { module.exports = {
...prettierConfig, ...prettierConfig,
importOrderParserPlugins: ['importAssertions', ...prettierConfig.importOrderParserPlugins], importOrderParserPlugins: [
'importAssertions',
// `using` keyword
'explicitResourceManagement',
...prettierConfig.importOrderParserPlugins,
],
plugins: [ plugins: [
'prettier-plugin-sql', 'prettier-plugin-sql',
...plugins, ...plugins,