diff --git a/packages/app-insights/package.json b/packages/app-insights/package.json index f47579828..ccc34f7ec 100644 --- a/packages/app-insights/package.json +++ b/packages/app-insights/package.json @@ -60,7 +60,7 @@ "@microsoft/applicationinsights-clickanalytics-js": "^3.0.2", "@microsoft/applicationinsights-react-js": "^17.0.0", "@microsoft/applicationinsights-web": "^3.0.2", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "applicationinsights": "^2.7.0" }, "peerDependencies": { diff --git a/packages/cli/package.json b/packages/cli/package.json index c3631e790..e88338f19 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -50,7 +50,7 @@ "@logto/phrases-experience": "workspace:^1.5.0", "@logto/schemas": "workspace:1.12.0", "@logto/shared": "workspace:^3.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "chalk": "^5.0.0", "decamelize": "^6.0.0", "dotenv": "^16.0.0", diff --git a/packages/connectors/templates/package.json b/packages/connectors/templates/package.json index 029c18e57..f7462fa39 100644 --- a/packages/connectors/templates/package.json +++ b/packages/connectors/templates/package.json @@ -23,7 +23,7 @@ "prepublishOnly": "pnpm build" }, "dependencies": { - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "got": "^14.0.0", "snakecase-keys": "^5.4.4", "zod": "^3.22.4" diff --git a/packages/console/package.json b/packages/console/package.json index fffc50eea..5bc45ecbe 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -44,7 +44,7 @@ "@parcel/transformer-svg-react": "2.9.3", "@silverhand/eslint-config": "5.0.0", "@silverhand/eslint-config-react": "5.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@silverhand/ts-config": "5.0.0", "@silverhand/ts-config-react": "5.0.0", "@swc/core": "^1.3.52", diff --git a/packages/core/package.json b/packages/core/package.json index 6615ebe1a..6f0b351f7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -43,7 +43,7 @@ "@logto/phrases-experience": "workspace:^1.5.0", "@logto/schemas": "workspace:^1.12.0", "@logto/shared": "workspace:^3.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@simplewebauthn/server": "^8.2.0", "@withtyped/client": "^0.7.22", "camelcase": "^8.0.0", diff --git a/packages/core/src/libraries/organization-invitation.ts b/packages/core/src/libraries/organization-invitation.ts index ed28d2828..edf741080 100644 --- a/packages/core/src/libraries/organization-invitation.ts +++ b/packages/core/src/libraries/organization-invitation.ts @@ -1,18 +1,34 @@ import { ConnectorType, TemplateType } from '@logto/connector-kit'; -import { OrganizationInvitationStatus, type CreateOrganizationInvitation } from '@logto/schemas'; +import { + OrganizationInvitationStatus, + type CreateOrganizationInvitation, + type OrganizationInvitationEntity, +} from '@logto/schemas'; import { generateStandardId } from '@logto/shared'; -import { appendPath } from '@silverhand/essentials'; +import { appendPath, removeUndefinedKeys } from '@silverhand/essentials'; import { EnvSet } from '#src/env-set/index.js'; import { getTenantEndpoint } from '#src/env-set/utils.js'; +import RequestError from '#src/errors/RequestError/index.js'; import MagicLinkQueries from '#src/queries/magic-link.js'; import OrganizationQueries from '#src/queries/organization/index.js'; +import { createUserQueries } from '#src/queries/user.js'; import type Queries from '#src/tenants/Queries.js'; import { type ConnectorLibrary } from './connector.js'; const invitationLinkPath = '/invitation'; +/** + * The ending statuses of an organization invitation per RFC 0003. It means that the invitation + * status cannot be changed anymore. + */ +const endingStatuses = Object.freeze([ + OrganizationInvitationStatus.Accepted, + OrganizationInvitationStatus.Expired, + OrganizationInvitationStatus.Revoked, +]); + /** Class for managing organization invitations. */ export class OrganizationInvitationLibrary { constructor( @@ -77,6 +93,108 @@ export class OrganizationInvitationLibrary { }); } + /** + * Revokes an organization invitation. The transaction will be rolled back if the status is one + * of the ending statuses. + * + * @param id The ID of the invitation. + * @param status The new status of the invitation. + * @returns A promise that resolves to the updated invitation. + * @see {@link endingStatuses} for the ending statuses. + */ + async updateStatus( + id: string, + status: OrganizationInvitationStatus.Revoked + ): Promise; + /** + * Updates the status of an organization invitation to `Accepted`, and assigns the user to the + * organization with the provided roles in the invitation. + * + * The transaction will be rolled back if: + * - The status is one of the ending statuses. + * - The `acceptedUserId` is not provided. + * - The `acceptedUserId` is not the same as the invitee. + * + * @param id The ID of the invitation. + * @param status The new status of the invitation (`Accepted`). + * @param acceptedUserId The user ID of the user who accepted the invitation. + * @returns A promise that resolves to the updated invitation. + * @see {@link endingStatuses} for the ending statuses. + */ + async updateStatus( + id: string, + status: OrganizationInvitationStatus.Accepted, + acceptedUserId: string + ): Promise; + // TODO: Error i18n + async updateStatus( + id: string, + status: OrganizationInvitationStatus, + acceptedUserId?: string + ): Promise { + const entity = await this.queries.organizations.invitations.findById(id); + + if (endingStatuses.includes(entity.status)) { + throw new RequestError({ + status: 422, + code: 'request.invalid_input', + details: 'The status of the invitation cannot be changed anymore.', + }); + } + + return this.queries.pool.transaction(async (connection) => { + const organizationQueries = new OrganizationQueries(connection); + const userQueries = createUserQueries(connection); + + switch (status) { + case OrganizationInvitationStatus.Accepted: { + // Normally this shouldn't happen, so we use `TypeError` instead of `RequestError`. + if (!acceptedUserId) { + throw new TypeError('The `acceptedUserId` is required when accepting an invitation.'); + } + + const user = await userQueries.findUserById(acceptedUserId); + + if (user.primaryEmail !== entity.invitee) { + throw new RequestError({ + status: 422, + code: 'request.invalid_input', + details: 'The accepted user must have the same email as the invitee.', + }); + } + + await organizationQueries.relations.users.insert([entity.organizationId, acceptedUserId]); + + if (entity.organizationRoles.length > 0) { + await organizationQueries.relations.rolesUsers.insert( + ...entity.organizationRoles.map<[string, string, string]>((role) => [ + entity.organizationId, + role.id, + acceptedUserId, + ]) + ); + } + break; + } + case OrganizationInvitationStatus.Revoked: { + break; + } + default: { + throw new TypeError(`The status "${status}" is not supported.`); + } + } + + const updated = { + status, + acceptedUserId, + updatedAt: Date.now(), + }; + await organizationQueries.invitations.updateById(id, updated); + + return { ...entity, ...removeUndefinedKeys(updated) }; + }); + } + protected async sendEmail(to: string, token: string) { const emailConnector = await this.connector.getMessageConnector(ConnectorType.Email); return emailConnector.sendMessage({ diff --git a/packages/core/src/queries/organization/index.ts b/packages/core/src/queries/organization/index.ts index 802cf2a55..c200765c0 100644 --- a/packages/core/src/queries/organization/index.ts +++ b/packages/core/src/queries/organization/index.ts @@ -17,6 +17,7 @@ import { type OrganizationInvitationEntity, MagicLinks, OrganizationInvitationRoleRelations, + OrganizationInvitationStatus, } from '@logto/schemas'; import { conditionalSql, convertToIdentifiers } from '@logto/shared'; import { sql, type CommonQueryMethods } from 'slonik'; @@ -121,7 +122,19 @@ class OrganizationInvitationsQueries extends SchemaQueries< return sql` select - ${table}.*, + ${sql.join( + Object.values(fields).filter((field) => field !== fields.status), + sql`, ` + )}, + -- Dynamically calculate the status of the invitation, note that the + -- actual status of the invitation is not changed. + case + when + ${fields.status} = ${OrganizationInvitationStatus.Pending} and + ${fields.expiresAt} < now() + then ${OrganizationInvitationStatus.Expired} + else ${fields.status} + end as "status", coalesce( json_agg( json_build_object( diff --git a/packages/core/src/routes/organization/invitations.openapi.json b/packages/core/src/routes/organization/invitations.openapi.json index 891a765a8..529225987 100644 --- a/packages/core/src/routes/organization/invitations.openapi.json +++ b/packages/core/src/routes/organization/invitations.openapi.json @@ -72,17 +72,6 @@ "get": { "summary": "Get organization invitation", "description": "Get an organization invitation by ID.", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The organization invitation ID.", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "The organization invitation, also contains the organization roles to be assigned to the user when they accept the invitation, and the corresponding magic link data." @@ -92,23 +81,41 @@ "delete": { "summary": "Delete organization invitation", "description": "Delete an organization invitation by ID.", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The organization invitation ID.", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "204": { "description": "The organization invitation was deleted successfully." } } } + }, + "/api/organization-invitations/{id}/status": { + "put": { + "summary": "Update organization invitation status", + "description": "Update the status of an organization invitation by ID.", + "requestBody": { + "description": "The organization invitation status to update.", + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "status": { + "description": "The status of the organization invitation." + }, + "acceptedUserId": { + "description": "The ID of the user who accepted the organization invitation. Required if the status is \"Accepted\"." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The organization invitation status was updated successfully." + } + } + } } } } diff --git a/packages/core/src/routes/organization/invitations.ts b/packages/core/src/routes/organization/invitations.ts index 79b160967..2aa38a7bf 100644 --- a/packages/core/src/routes/organization/invitations.ts +++ b/packages/core/src/routes/organization/invitations.ts @@ -1,4 +1,8 @@ -import { OrganizationInvitations, organizationInvitationEntityGuard } from '@logto/schemas'; +import { + OrganizationInvitationStatus, + OrganizationInvitations, + organizationInvitationEntityGuard, +} from '@logto/schemas'; import { yes } from '@silverhand/essentials'; import { z } from 'zod'; @@ -51,7 +55,7 @@ export default function organizationInvitationRoutes( response: organizationInvitationEntityGuard, status: [201], }), - async (ctx) => { + async (ctx, next) => { const { query, body } = ctx.guard; assertThat( @@ -64,6 +68,50 @@ export default function organizationInvitationRoutes( ctx.body = await organizationInvitations.insert(body, yes(query.skipEmail)); ctx.status = 201; + return next(); + } + ); + + router.put( + '/:id/status', + koaGuard({ + params: z.object({ + id: z.string(), + }), + body: OrganizationInvitations.updateGuard + .pick({ + acceptedUserId: true, + }) + .extend({ + status: z.enum([ + OrganizationInvitationStatus.Accepted, + OrganizationInvitationStatus.Revoked, + ]), + }), + response: organizationInvitationEntityGuard, + status: [200], + }), + async (ctx, next) => { + const { id } = ctx.guard.params; + const { status, acceptedUserId } = ctx.guard.body; + + if (status !== OrganizationInvitationStatus.Accepted) { + ctx.body = await organizationInvitations.updateStatus(id, status); + return next(); + } + + // TODO: Error i18n + assertThat( + acceptedUserId, + new RequestError({ + status: 422, + code: 'request.invalid_input', + details: 'The `acceptedUserId` is required when accepting an invitation.', + }) + ); + + ctx.body = await organizationInvitations.updateStatus(id, status, acceptedUserId); + return next(); } ); diff --git a/packages/experience/package.json b/packages/experience/package.json index 8433b0509..936735294 100644 --- a/packages/experience/package.json +++ b/packages/experience/package.json @@ -37,7 +37,7 @@ "@react-spring/web": "^9.6.1", "@silverhand/eslint-config": "5.0.0", "@silverhand/eslint-config-react": "5.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@silverhand/ts-config": "5.0.0", "@silverhand/ts-config-react": "5.0.0", "@simplewebauthn/browser": "^8.3.1", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 12dbc40a9..56c72e208 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -31,7 +31,7 @@ "@logto/schemas": "workspace:^1.12.0", "@logto/shared": "workspace:^3.0.0", "@silverhand/eslint-config": "5.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@silverhand/ts-config": "5.0.0", "@types/jest": "^29.4.0", "@types/node": "^20.9.5", diff --git a/packages/integration-tests/src/api/organization-invitation.ts b/packages/integration-tests/src/api/organization-invitation.ts index 2dc219b4b..3d3cc71e5 100644 --- a/packages/integration-tests/src/api/organization-invitation.ts +++ b/packages/integration-tests/src/api/organization-invitation.ts @@ -1,4 +1,7 @@ -import { type OrganizationInvitationEntity } from '@logto/schemas'; +import { + type OrganizationInvitationStatus, + type OrganizationInvitationEntity, +} from '@logto/schemas'; import { authedAdminApi } from './api.js'; import { ApiFactory } from './factory.js'; @@ -29,4 +32,15 @@ export class OrganizationInvitationApi extends ApiFactory< }) .json(); } + + async updateStatus(id: string, status: OrganizationInvitationStatus, acceptedUserId?: string) { + return authedAdminApi + .put(`${this.path}/${id}/status`, { + json: { + status, + acceptedUserId, + }, + }) + .json(); + } } diff --git a/packages/integration-tests/src/tests/api/organization/organization-invitation.test.ts b/packages/integration-tests/src/tests/api/organization/organization-invitation.creation.test.ts similarity index 98% rename from packages/integration-tests/src/tests/api/organization/organization-invitation.test.ts rename to packages/integration-tests/src/tests/api/organization/organization-invitation.creation.test.ts index 239df4cc4..2581afe3a 100644 --- a/packages/integration-tests/src/tests/api/organization/organization-invitation.test.ts +++ b/packages/integration-tests/src/tests/api/organization/organization-invitation.creation.test.ts @@ -15,7 +15,7 @@ const expectErrorResponse = (error: unknown, status: number, code: string) => { expect(body).toMatchObject({ code }); }; -describe('organization invitations', () => { +describe('organization invitation creation', () => { const invitationApi = new OrganizationInvitationApiTest(); const organizationApi = new OrganizationApiTest(); diff --git a/packages/integration-tests/src/tests/api/organization/organization-invitation.status.test.ts b/packages/integration-tests/src/tests/api/organization/organization-invitation.status.test.ts new file mode 100644 index 000000000..a23f9ce81 --- /dev/null +++ b/packages/integration-tests/src/tests/api/organization/organization-invitation.status.test.ts @@ -0,0 +1,187 @@ +import assert from 'node:assert'; + +import { OrganizationInvitationStatus } from '@logto/schemas'; +import { generateStandardId } from '@logto/shared'; +import { HTTPError } from 'got'; + +import { OrganizationApiTest, OrganizationInvitationApiTest } from '#src/helpers/organization.js'; +import { UserApiTest } from '#src/helpers/user.js'; + +const randomId = () => generateStandardId(4); + +const expectErrorResponse = (error: unknown, status: number, code: string) => { + assert(error instanceof HTTPError); + const { statusCode, body: raw } = error.response; + const body: unknown = JSON.parse(String(raw)); + expect(statusCode).toBe(status); + expect(body).toMatchObject({ code }); +}; + +describe('organization invitation status update', () => { + const invitationApi = new OrganizationInvitationApiTest(); + const organizationApi = new OrganizationApiTest(); + const userApi = new UserApiTest(); + + afterEach(async () => { + await Promise.all([ + organizationApi.cleanUp(), + organizationApi.roleApi.cleanUp(), + invitationApi.cleanUp(), + userApi.cleanUp(), + ]); + }); + + it('should expire invitations and disable update after the expiration date', async () => { + const organization = await organizationApi.create({ name: 'test' }); + const invitation = await invitationApi.create( + { + organizationId: organization.id, + invitee: `${randomId()}@example.com`, + expiresAt: Date.now() + 100, + }, + true + ); + expect(invitation.status).toBe('Pending'); + + await new Promise((resolve) => { + setTimeout(resolve, 200); + }); + + const invitationById = await invitationApi.get(invitation.id); + expect(invitationById.status).toBe('Expired'); + + const error = await invitationApi + .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted) + .catch((error: unknown) => error); + expectErrorResponse(error, 422, 'request.invalid_input'); + }); + + it('should be able to accept an invitation', async () => { + const organization = await organizationApi.create({ name: 'test' }); + const invitation = await invitationApi.create( + { + organizationId: organization.id, + invitee: `${randomId()}@example.com`, + expiresAt: Date.now() + 1_000_000, + }, + true + ); + expect(invitation.status).toBe('Pending'); + + const user = await userApi.create({ + primaryEmail: invitation.invitee, + }); + const updated = await invitationApi.updateStatus( + invitation.id, + OrganizationInvitationStatus.Accepted, + user.id + ); + + expect(updated.status).toBe('Accepted'); + + const userOrganizations = await organizationApi.getUserOrganizations(user.id); + expect(userOrganizations).toContainEqual( + expect.objectContaining({ + id: organization.id, + name: organization.name, + }) + ); + }); + + it('should be able to accept an invitation with roles', async () => { + const organization = await organizationApi.create({ name: 'test' }); + const role = await organizationApi.roleApi.create({ name: 'test' }); + const invitation = await invitationApi.create( + { + organizationId: organization.id, + invitee: `${randomId()}@example.com`, + expiresAt: Date.now() + 1_000_000, + organizationRoleIds: [role.id], + }, + true + ); + expect(invitation.status).toBe('Pending'); + + const user = await userApi.create({ + primaryEmail: invitation.invitee, + }); + const updated = await invitationApi.updateStatus( + invitation.id, + OrganizationInvitationStatus.Accepted, + user.id + ); + + expect(updated.status).toBe('Accepted'); + + const userOrganizations = await organizationApi.getUserOrganizations(user.id); + expect(userOrganizations).toContainEqual( + expect.objectContaining({ + id: organization.id, + name: organization.name, + organizationRoles: [expect.objectContaining({ id: role.id, name: role.name })], + }) + ); + }); + + it('should not be able to accept an invitation with a different email', async () => { + const organization = await organizationApi.create({ name: 'test' }); + const invitation = await invitationApi.create( + { + organizationId: organization.id, + invitee: `${randomId()}@example.com`, + expiresAt: Date.now() + 1_000_000, + }, + true + ); + expect(invitation.status).toBe('Pending'); + + const user = await userApi.create({ + primaryEmail: `${randomId()}@example.com`, + }); + const error = await invitationApi + .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted, user.id) + .catch((error: unknown) => error); + + expectErrorResponse(error, 422, 'request.invalid_input'); + }); + + it('should not be able to accept an invitation with an invalid user id', async () => { + const organization = await organizationApi.create({ name: 'test' }); + const invitation = await invitationApi.create( + { + organizationId: organization.id, + invitee: `${randomId()}@example.com`, + expiresAt: Date.now() + 1_000_000, + }, + true + ); + expect(invitation.status).toBe('Pending'); + + const error = await invitationApi + .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted, 'invalid') + .catch((error: unknown) => error); + + expectErrorResponse(error, 404, 'entity.not_found'); + }); + + it('should not be able to update the status of an ended invitation', async () => { + const organization = await organizationApi.create({ name: 'test' }); + const invitation = await invitationApi.create( + { + organizationId: organization.id, + invitee: `${randomId()}@example.com`, + expiresAt: Date.now() + 1_000_000, + }, + true + ); + expect(invitation.status).toBe('Pending'); + + await invitationApi.updateStatus(invitation.id, OrganizationInvitationStatus.Revoked); + + const error = await invitationApi + .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted) + .catch((error: unknown) => error); + + expectErrorResponse(error, 422, 'request.invalid_input'); + }); +}); diff --git a/packages/phrases-experience/package.json b/packages/phrases-experience/package.json index fc007f8bd..2daf9d13d 100644 --- a/packages/phrases-experience/package.json +++ b/packages/phrases-experience/package.json @@ -35,7 +35,7 @@ "dependencies": { "@logto/core-kit": "workspace:^2.2.1", "@logto/language-kit": "workspace:^1.0.0", - "@silverhand/essentials": "^2.8.4" + "@silverhand/essentials": "^2.8.8" }, "peerDependencies": { "zod": "^3.22.4" diff --git a/packages/phrases/package.json b/packages/phrases/package.json index 5ceee1fe5..f5342d92b 100644 --- a/packages/phrases/package.json +++ b/packages/phrases/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@logto/language-kit": "workspace:^1.0.0", - "@silverhand/essentials": "^2.8.4" + "@silverhand/essentials": "^2.8.8" }, "peerDependencies": { "zod": "^3.22.4" diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 97cd2174b..e534e774b 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@silverhand/eslint-config": "5.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@silverhand/ts-config": "5.0.0", "@types/inquirer": "^9.0.0", "@types/jest": "^29.4.0", diff --git a/packages/schemas/src/types/organization.ts b/packages/schemas/src/types/organization.ts index 62ff95733..e46042591 100644 --- a/packages/schemas/src/types/organization.ts +++ b/packages/schemas/src/types/organization.ts @@ -93,12 +93,12 @@ export type OrganizationWithFeatured = Organization & { * - `organizationRoles`: The roles to be assigned to the user when accepting the invitation. */ export type OrganizationInvitationEntity = OrganizationInvitation & { - magicLink: MagicLink; + magicLink?: MagicLink; organizationRoles: OrganizationRoleEntity[]; }; export const organizationInvitationEntityGuard: z.ZodType = OrganizationInvitations.guard.extend({ - magicLink: MagicLinks.guard, + magicLink: MagicLinks.guard.optional(), organizationRoles: organizationRoleEntityGuard.array(), }); diff --git a/packages/shared/package.json b/packages/shared/package.json index 8e47b3da1..5ec1ca002 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -60,7 +60,7 @@ }, "prettier": "@silverhand/eslint-config/.prettierrc", "dependencies": { - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "chalk": "^5.0.0", "find-up": "^7.0.0", "libphonenumber-js": "^1.9.49", diff --git a/packages/toolkit/connector-kit/package.json b/packages/toolkit/connector-kit/package.json index 001bdd847..1ea2c2e79 100644 --- a/packages/toolkit/connector-kit/package.json +++ b/packages/toolkit/connector-kit/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@logto/language-kit": "workspace:^1.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@withtyped/client": "^0.7.22", "@withtyped/server": "^0.12.9" }, diff --git a/packages/toolkit/core-kit/package.json b/packages/toolkit/core-kit/package.json index 957f74233..3de5e7eec 100644 --- a/packages/toolkit/core-kit/package.json +++ b/packages/toolkit/core-kit/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@jest/types": "^29.0.3", "@silverhand/eslint-config": "5.0.0", - "@silverhand/essentials": "^2.8.4", + "@silverhand/essentials": "^2.8.8", "@silverhand/ts-config": "5.0.0", "@silverhand/ts-config-react": "5.0.0", "@types/color": "^3.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87d09e06e..07d9de08f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,8 +49,8 @@ importers: specifier: ^3.0.2 version: 3.0.2(tslib@2.4.1)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 applicationinsights: specifier: ^2.7.0 version: 2.7.0 @@ -125,8 +125,8 @@ importers: specifier: workspace:^3.0.0 version: link:../shared '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 chalk: specifier: ^5.0.0 version: 5.1.2 @@ -243,8 +243,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 dayjs: specifier: ^1.10.5 version: 1.11.6 @@ -331,8 +331,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 dayjs: specifier: ^1.10.5 version: 1.11.6 @@ -419,8 +419,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -498,8 +498,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -580,8 +580,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../shared '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -668,8 +668,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -750,8 +750,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -829,8 +829,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -908,8 +908,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -987,8 +987,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1066,8 +1066,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1148,8 +1148,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1227,8 +1227,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1306,8 +1306,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1388,8 +1388,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1467,8 +1467,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1546,8 +1546,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1625,8 +1625,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1704,8 +1704,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1783,8 +1783,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1862,8 +1862,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -1941,8 +1941,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2020,8 +2020,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2105,8 +2105,8 @@ importers: specifier: workspace:^3.0.0 version: link:../../shared '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2190,8 +2190,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 fast-xml-parser: specifier: ^4.2.5 version: 4.2.5 @@ -2275,8 +2275,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2354,8 +2354,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2433,8 +2433,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2518,8 +2518,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2597,8 +2597,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2676,8 +2676,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2755,8 +2755,8 @@ importers: specifier: workspace:^2.0.0 version: link:../../toolkit/connector-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.6 + specifier: ^2.8.8 + version: 2.8.8 got: specifier: ^14.0.0 version: 14.0.0 @@ -2894,8 +2894,8 @@ importers: specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(postcss@8.4.31)(prettier@3.0.0)(stylelint@15.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@silverhand/ts-config': specifier: 5.0.0 version: 5.0.0(typescript@5.3.3) @@ -3182,8 +3182,8 @@ importers: specifier: workspace:^3.0.0 version: link:../shared '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@simplewebauthn/server': specifier: ^8.2.0 version: 8.2.0 @@ -3570,8 +3570,8 @@ importers: specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(postcss@8.4.31)(prettier@3.0.0)(stylelint@15.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@silverhand/ts-config': specifier: 5.0.0 version: 5.0.0(typescript@5.3.3) @@ -3772,8 +3772,8 @@ importers: specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(prettier@3.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@silverhand/ts-config': specifier: 5.0.0 version: 5.0.0(typescript@5.3.3) @@ -3835,8 +3835,8 @@ importers: specifier: workspace:^1.0.0 version: link:../toolkit/language-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 zod: specifier: ^3.22.4 version: 3.22.4 @@ -3869,8 +3869,8 @@ importers: specifier: workspace:^1.0.0 version: link:../toolkit/language-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 zod: specifier: ^3.22.4 version: 3.22.4 @@ -3928,8 +3928,8 @@ importers: specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(prettier@3.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@silverhand/ts-config': specifier: 5.0.0 version: 5.0.0(typescript@5.3.3) @@ -3982,8 +3982,8 @@ importers: packages/shared: dependencies: '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 chalk: specifier: ^5.0.0 version: 5.1.2 @@ -4037,8 +4037,8 @@ importers: specifier: workspace:^1.0.0 version: link:../language-kit '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@withtyped/client': specifier: ^0.7.22 version: 0.7.22(zod@3.22.4) @@ -4107,8 +4107,8 @@ importers: specifier: 5.0.0 version: 5.0.0(eslint@8.44.0)(prettier@3.0.0)(typescript@5.3.3) '@silverhand/essentials': - specifier: ^2.8.4 - version: 2.8.4 + specifier: ^2.8.8 + version: 2.8.8 '@silverhand/ts-config': specifier: 5.0.0 version: 5.0.0(typescript@5.3.3) @@ -7569,7 +7569,7 @@ packages: resolution: {integrity: sha512-yDWSZMI2Qo/xoYU92tnwSP/gnSvq8+CLK5DqD/4brO42QJa7xjt7eA+HSyuMmSUrKffY2nP3riU81gs+nR8DkA==} engines: {node: ^18.12.0} dependencies: - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 tiny-cookie: 2.4.1 dev: false @@ -7577,7 +7577,7 @@ packages: resolution: {integrity: sha512-8kKh1EcAm19smnEMvw0M51d2EQXEEH77G1JEKh1iLifUScmxD+c7HcN/5mLekaBz36MUVNiA2gE7s9L2GOpWrg==} dependencies: '@logto/client': 2.3.0 - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 js-base64: 3.7.5 dev: true @@ -7585,7 +7585,7 @@ packages: resolution: {integrity: sha512-VrzsF+QtnrVXnDFbsdYTeGatjThlTFwtjTT/jJMaFdyRg0lno8vHxsjuyG8ba4wVSu22tSmJAr7okpAwRyhtcg==} dependencies: '@logto/js': 3.0.1 - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 camelcase-keys: 7.0.2 jose: 5.0.1 dev: true @@ -7594,7 +7594,7 @@ packages: resolution: {integrity: sha512-cDKxCBFeZcG0CiGIa6mBY1Zgu3+tuvhYxs/qN0nQcj7MLSQ0AXHK9m1GeSJQH+Nu/jspggLmg27Ks8gdCHQUcg==} engines: {node: ^20.9.0} dependencies: - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 '@withtyped/server': 0.12.9(zod@3.22.4) transitivePeerDependencies: - zod @@ -7603,7 +7603,7 @@ packages: /@logto/js@3.0.1: resolution: {integrity: sha512-vsU6mH5oiiW3k00pMyVA4V31K2Bd0rOT9qWch2l5e5o1yCQLJ3zUIOjGjChu3m2TRu1d920iiUpZU3Lzf6Pwdw==} dependencies: - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 camelcase-keys: 7.0.2 jose: 5.0.1 dev: true @@ -7612,7 +7612,7 @@ packages: resolution: {integrity: sha512-xzVCnlrev/bqLtXOAw9I35h7njU1bed1jt6fL/VZfY4RUUJVZ36LG7fq2b7GsGg5xGRLchJ//+/VP1ac3+27YA==} dependencies: '@logto/client': 2.3.0 - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 js-base64: 3.7.5 node-fetch: 2.7.0 transitivePeerDependencies: @@ -7625,7 +7625,7 @@ packages: react: '>=16.8.0 || ^18.0.0' dependencies: '@logto/browser': 2.2.0 - '@silverhand/essentials': 2.8.6 + '@silverhand/essentials': 2.8.8 react: 18.2.0 dev: true @@ -9298,13 +9298,9 @@ packages: lodash: 4.17.21 dev: true - /@silverhand/essentials@2.8.4: - resolution: {integrity: sha512-VaI00QyD2trA7n7/wHNcGNGRXoSr8dUGs/hQCu4Rju4Edl3vso7CeCXdfGU2aNDuT2uMs75of6Ph8gqVJhWlYQ==} - engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0} - - /@silverhand/essentials@2.8.6: - resolution: {integrity: sha512-qNyc6CvZRngP66hTA8sbpXGsiu9j6Hu62jCy7LV3tJiytg/hmxJB5oM9k5tpaUa9kHwYJ2X9CManX7SYYNrzHg==} - engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0} + /@silverhand/essentials@2.8.8: + resolution: {integrity: sha512-JCiNjdF9IcsF/7Gd4ZMAL3ykdujKrIvEpKa2Iz6YqUaoCSJEeyLwspE5xAHo38a0oT55Qa2pC+wLjEEgWAD8zA==} + engines: {node: ^18.12.0 || ^20.9.0, pnpm: ^8.0.0} /@silverhand/ts-config-react@5.0.0(typescript@5.3.3): resolution: {integrity: sha512-DQpaG49sY36FhpyFIRAmTA5bG9ASFUVXu+yi29bMdLls51r9nEoBuWxWv2Sh3GnSGxKLq4gmFf+U8Zy/qcnX1w==} @@ -10466,7 +10462,7 @@ packages: peerDependencies: zod: ^3.19.1 dependencies: - '@silverhand/essentials': 2.8.4 + '@silverhand/essentials': 2.8.8 '@withtyped/shared': 0.2.2 zod: 3.22.4