diff --git a/.env.example b/.env.example index 3d87d72d..2c3048a9 100644 --- a/.env.example +++ b/.env.example @@ -109,6 +109,43 @@ DISCORD_PUBLIC_KEY= # OPTIONAL: OAuth redirect URL. Defaults to /discord/oauth/callback. DISCORD_OAUTH_REDIRECT_URL= +# [[GITHUB APP INTEGRATION]] +# These settings power the two-way GitHub issue integration and are separate +# from GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET under SOCIAL AUTH, which are +# used only for signing users into Feeblo. +# +# Create a GitHub App under GitHub Settings -> Developer settings -> GitHub +# Apps. Give it repository permissions "Issues: Read and write" and +# "Metadata: Read", subscribe it to the Issues event, and enable +# "Request user authorization (OAuth) during installation". Leave every +# value below empty to disable the GitHub integration. +# +# REQUIRED: Numeric App ID shown on the GitHub App's General page. This is not +# the OAuth Client ID. +GITHUB_INTEGRATION_APP_ID= +# REQUIRED: URL-friendly app name from the public installation URL. For +# https://github.com/apps/feeblo, the slug is "feeblo". +GITHUB_INTEGRATION_APP_SLUG= +# REQUIRED: Client ID and client secret from the GitHub App's General page. +# They are used only during installation to verify that the current installer +# can access the selected GitHub App installation. The temporary user token is +# discarded and is never stored. +GITHUB_INTEGRATION_CLIENT_ID= +GITHUB_INTEGRATION_CLIENT_SECRET= +# REQUIRED: PEM private key generated from the GitHub App's Private keys +# section. Preserve the complete BEGIN/END lines and line breaks. In a secret +# manager, store the PEM as a multiline secret rather than committing the +# downloaded .pem file. +GITHUB_INTEGRATION_PRIVATE_KEY= +# REQUIRED: Random webhook secret configured identically on the GitHub App's +# General page. GitHub signs the global App webhook with this value. +# Generate one with: openssl rand -hex 32 +# Also register the fixed Feeblo endpoints in the GitHub App: the callback URL +# /github/app/installations/callback and the webhook URL +# /github/app/webhooks. Feeblo mounts both paths itself; there are no +# override variables for them. +GITHUB_INTEGRATION_WEBHOOK_SECRET= + # [[BILLING]] # OPTIONAL: Enables Polar checkout, customer portal, and signed webhooks when # both credentials are configured. Use sandbox while testing. diff --git a/apps/server/package.json b/apps/server/package.json index eb31785a..26ca717c 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -7,7 +7,8 @@ "build": "dotenvx run -f ../../.env -- rolldown -c", "dev": "dotenvx run -f ../../.env -- tsx watch src/index.ts", "start": "dotenvx run -f ../../.env -- tsx src/index.ts", - "check-types": "tsc --noEmit" + "check-types": "tsc --noEmit", + "test": "vitest run" }, "dependencies": { "@effect/platform-node": "catalog:", @@ -18,6 +19,7 @@ "@feeblo/id": "workspace:*", "@feeblo/integration-core": "workspace:*", "@feeblo/integration-discord": "workspace:*", + "@feeblo/integration-github": "workspace:*", "@feeblo/integration-webhook": "workspace:*", "@feeblo/transactional": "workspace:*", "@feeblo/utils": "workspace:*", @@ -27,9 +29,11 @@ "@feeblo/integration-slack": "workspace:*" }, "devDependencies": { + "@effect/vitest": "catalog:", "@feeblo/config": "workspace:*", "rolldown": "catalog:", "tsx": "catalog:", - "typescript": "catalog:" + "typescript": "catalog:", + "vitest": "catalog:" } } diff --git a/apps/server/src/config.test.ts b/apps/server/src/config.test.ts index 35ebc6be..abe89f5f 100644 --- a/apps/server/src/config.test.ts +++ b/apps/server/src/config.test.ts @@ -9,6 +9,7 @@ import { ServerConfig } from "./config"; const requiredServerEnvironment = { APP_ROOT_DOMAIN: "example.test", APP_URL: "https://example.test", + AUTH_ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef", API_URL: "https://api.example.test", }; diff --git a/apps/server/src/config.ts b/apps/server/src/config.ts index fa628a46..af7f2eae 100644 --- a/apps/server/src/config.ts +++ b/apps/server/src/config.ts @@ -4,6 +4,7 @@ import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; +import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; export class ServerConfig extends Context.Service()( @@ -16,6 +17,44 @@ export class ServerConfig extends Context.Service()( const nodeEnv = yield* Config.string("NODE_ENV").pipe( Config.withDefault("development") ); + const githubAppId = yield* Config.string( + "GITHUB_INTEGRATION_APP_ID" + ).pipe(Config.option, Effect.map(Option.getOrUndefined)); + const githubAppSlug = yield* Config.string( + "GITHUB_INTEGRATION_APP_SLUG" + ).pipe(Config.option, Effect.map(Option.getOrUndefined)); + const githubClientId = yield* Config.string( + "GITHUB_INTEGRATION_CLIENT_ID" + ).pipe(Config.option, Effect.map(Option.getOrUndefined)); + const githubClientSecret = yield* Config.redacted( + "GITHUB_INTEGRATION_CLIENT_SECRET" + ).pipe( + Config.option, + Effect.map((value) => Option.getOrElse(value, () => Redacted.make(""))) + ); + const githubWebhookSecret = yield* Config.redacted( + "GITHUB_INTEGRATION_WEBHOOK_SECRET" + ).pipe( + Config.option, + Effect.map((value) => Option.getOrElse(value, () => Redacted.make(""))) + ); + const githubPrivateKey = yield* Config.redacted( + "GITHUB_INTEGRATION_PRIVATE_KEY" + ).pipe( + Config.option, + Effect.map((value) => Option.getOrElse(value, () => Redacted.make(""))) + ); + const githubEncryptionKey = yield* Config.redacted( + "INTEGRATION_ENCRYPTION_KEY" + ).pipe( + Config.option, + Effect.flatMap( + Option.match({ + onNone: () => Config.redacted("AUTH_ENCRYPTION_KEY"), + onSome: Effect.succeed, + }) + ) + ); // Outbound-webhook security configuration (encryption key and egress // policy) is owned by WebhookIntegrationConfig in the domain package. const integrationConnectionConcurrency = yield* Config.schema( @@ -68,6 +107,13 @@ export class ServerConfig extends Context.Service()( appUrl, appRootDomain, clientIpProxyTrust, + githubAppId, + githubAppSlug, + githubClientId, + githubClientSecret, + githubEncryptionKey, + githubPrivateKey, + githubWebhookSecret, integrationConnectionConcurrency, integrationGlobalConcurrency, nodeEnv, diff --git a/apps/server/src/discord.ts b/apps/server/src/discord.ts index d8583571..f03a9d6f 100644 --- a/apps/server/src/discord.ts +++ b/apps/server/src/discord.ts @@ -8,7 +8,10 @@ import { import type { IntegrationProviderRegistry } from "@feeblo/integration-core"; import { DiscordOAuthState } from "@feeblo/integration-discord"; import type { ParsedDiscordInboundRequest } from "@feeblo/integration-discord/inbound-schema"; -import { discordProviderKey } from "@feeblo/integration-discord/manifest"; +import { + discordInteractionsCapabilityKey, + discordProviderKey, +} from "@feeblo/integration-discord/manifest"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Layer from "effect/Layer"; @@ -55,7 +58,7 @@ const handleInteraction = ( ) => Effect.gen(function* () { const inboundHandler = registry.getInboundHandler({ - capabilityKey: "interactions", + capabilityKey: discordInteractionsCapabilityKey, provider: discordProviderKey, }); if (inboundHandler === undefined) { diff --git a/apps/server/src/github-provider.ts b/apps/server/src/github-provider.ts new file mode 100644 index 00000000..86d6ce96 --- /dev/null +++ b/apps/server/src/github-provider.ts @@ -0,0 +1,620 @@ +import { timingSafeEqual } from "node:crypto"; +import { + currentDb, + gitHubIssueSafeMetadataConditions, + schema, +} from "@feeblo/db"; +import { GitHubIntegrationConfig } from "@feeblo/domain/integration/github/config"; +import { GitHubProvider } from "@feeblo/domain/integration/github/github-provider"; +import { + BadRequestError, + InternalServerError, + NotFoundError, + UnauthorizedError, +} from "@feeblo/domain/rpc-errors"; +import { IntegrationConnectionId } from "@feeblo/id"; +import { + IntegrationOAuthState, + IntegrationProviderAuthenticationError, + IntegrationProviderInvalidConfigurationError, + IntegrationProviderPermanentRejection, +} from "@feeblo/integration-core"; +import { + createGitHubAppJwt, + decryptGitHubCredentialMaterial, + encryptGitHubCredentialMaterial, + type GitHubUserInstallation, + makeGitHubApiClient, + makeGitHubInstallationTokenResolver, + renderGitHubIssueBody, + renderGitHubIssueTitle, +} from "@feeblo/integration-github"; +import { githubProviderKey } from "@feeblo/integration-github/manifest"; +import { and, eq } from "drizzle-orm"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Redacted from "effect/Redacted"; +import * as Schema from "effect/Schema"; +import { ServerConfig } from "./config"; + +/** Server-owned GitHub App adapter. Durable state is installation identity only; all bearer tokens are ephemeral. */ +export const GitHubProviderLive = Layer.effect( + GitHubProvider, + Effect.gen(function* () { + const db = yield* currentDb; + const config = yield* ServerConfig; + const domainConfig = yield* GitHubIntegrationConfig; + const api = makeGitHubApiClient(); + const installationTokens = yield* makeGitHubInstallationTokenResolver({ + apiClient: api, + appId: config.githubAppId ?? "", + privateKey: config.githubPrivateKey, + }); + const integrationKey = config.githubEncryptionKey; + const providerFailure = (operation: string) => + new InternalServerError({ message: `GitHub App ${operation} failed.` }); + const issueFailure = (operation: string) => (failure: unknown) => { + if (Schema.is(NotFoundError)(failure)) { + return new NotFoundError({ + message: `GitHub resource was not found during ${operation}.`, + }); + } + if (Schema.is(IntegrationProviderAuthenticationError)(failure)) { + return new UnauthorizedError({ + message: `GitHub authentication failed during ${operation}.`, + }); + } + if (Schema.is(IntegrationProviderInvalidConfigurationError)(failure)) { + return new NotFoundError({ + message: `GitHub resource was not found during ${operation}.`, + }); + } + if (Schema.is(IntegrationProviderPermanentRejection)(failure)) { + return new BadRequestError({ + message: `GitHub rejected ${operation}.`, + }); + } + // Rate-limited and temporary/transport failures are indeterminate: the + // issue may already exist, so callers must retain idempotency state. + return providerFailure(operation); + }; + const installationIdForConnection = (connectionId: string) => + Effect.gen(function* () { + const [installation] = yield* db + .select({ + installationId: schema.githubInstallationTable.installationId, + }) + .from(schema.githubInstallationTable) + .where(eq(schema.githubInstallationTable.connectionId, connectionId)) + .limit(1) + .pipe(Effect.mapError(() => providerFailure("installation lookup"))); + if (installation === undefined) { + return yield* new NotFoundError({ + message: "GitHub App installation was not found.", + }); + } + return installation.installationId; + }); + const installationTokenForConnection = (connectionId: string) => + Effect.gen(function* () { + const installationId = yield* installationIdForConnection(connectionId); + // Typed token failures reach issueFailure unchanged so auth failures + // stay Unauthorized and missing installations stay NotFound. + return yield* installationTokens.getInstallationAccessToken({ + installationId, + }); + }); + const provider = GitHubProvider.of({ + startInstallation: (organizationId) => + Effect.gen(function* () { + if (!domainConfig.configured || config.githubAppSlug === undefined) { + return yield* new InternalServerError({ + message: "GitHub App integration is not configured.", + }); + } + const id = yield* IntegrationConnectionId.generate.pipe( + Effect.mapError(() => providerFailure("connection id generation")) + ); + const nonce = yield* Effect.try({ + try: () => crypto.randomUUID(), + catch: () => providerFailure("installation state generation"), + }); + const state = yield* Schema.encodeEffect( + Schema.fromJsonString(IntegrationOAuthState) + )({ connectionId: id, organizationId, nonce }).pipe( + Effect.mapError(() => + providerFailure("installation state encoding") + ) + ); + const ciphertext = yield* encryptGitHubCredentialMaterial( + integrationKey, + { + installationState: nonce, + } + ).pipe( + Effect.mapError(() => + providerFailure("installation state encryption") + ) + ); + yield* db + .insert(schema.integrationConnectionTable) + .values({ + id, + organizationId, + provider: githubProviderKey, + name: "GitHub", + lifecycle: "connecting", + credentialGeneration: 1, + credentialsCiphertext: ciphertext, + safeDisplayMetadata: {}, + }) + .pipe( + Effect.mapError(() => providerFailure("connection creation")) + ); + const installUrl = new URL( + `https://github.com/apps/${encodeURIComponent(config.githubAppSlug)}/installations/new` + ); + installUrl.searchParams.set("state", state); + return { authorizeUrl: installUrl }; + }), + completeInstallation: (input) => + Effect.gen(function* () { + if (config.githubClientId === undefined || !domainConfig.configured) { + return yield* new InternalServerError({ + message: "GitHub App integration is not configured.", + }); + } + const parsed = yield* Schema.decodeUnknownEffect( + Schema.fromJsonString(IntegrationOAuthState) + )(input.state).pipe( + Effect.mapError(() => + providerFailure("installation state validation") + ) + ); + const [connection] = yield* db + .select() + .from(schema.integrationConnectionTable) + .where( + and( + eq(schema.integrationConnectionTable.id, parsed.connectionId), + eq( + schema.integrationConnectionTable.organizationId, + parsed.organizationId + ), + eq( + schema.integrationConnectionTable.provider, + githubProviderKey + ), + eq(schema.integrationConnectionTable.lifecycle, "connecting") + ) + ) + .limit(1) + .pipe( + Effect.mapError(() => + providerFailure("installation connection lookup") + ) + ); + if ( + connection?.credentialsCiphertext === null || + connection?.credentialsCiphertext === undefined + ) { + return yield* providerFailure("installation connection lookup"); + } + const credentialsCiphertext = connection.credentialsCiphertext; + const pending = yield* decryptGitHubCredentialMaterial( + integrationKey, + connection.credentialsCiphertext + ).pipe( + Effect.mapError(() => + providerFailure("installation state decryption") + ) + ); + const expectedState = Buffer.from(pending.installationState ?? ""); + const receivedState = Buffer.from(parsed.nonce); + if ( + expectedState.length === 0 || + expectedState.length !== receivedState.length || + !timingSafeEqual(expectedState, receivedState) + ) { + return yield* providerFailure("installation state validation"); + } + const userToken = yield* api + .exchangeUserAccessToken({ + clientId: config.githubClientId, + clientSecret: config.githubClientSecret, + code: input.code, + }) + .pipe( + Effect.mapError(() => providerFailure("installer token exchange")) + ); + const installerAccessToken = Redacted.make(userToken.access_token); + let installation: GitHubUserInstallation | undefined; + for (let page = 1; installation === undefined; page += 1) { + const accessible = yield* api + .listUserInstallations({ + accessToken: installerAccessToken, + page, + }) + .pipe( + Effect.mapError(() => + providerFailure("installer installation verification") + ) + ); + installation = accessible.installations.find( + (candidate) => String(candidate.id) === input.installationId + ); + if ( + installation !== undefined || + accessible.installations.length < 100 || + page * 100 >= accessible.total_count + ) { + break; + } + } + if (installation === undefined) { + return yield* new NotFoundError({ + message: + "GitHub App installation is not accessible to the installer.", + }); + } + if (installation.account === null) { + return yield* new NotFoundError({ + message: "GitHub App installation account is unavailable.", + }); + } + const installationAccount = installation.account; + yield* db + .transaction(() => + Effect.gen(function* () { + const [existingInstallation] = yield* db + .select({ + connectionId: schema.githubInstallationTable.connectionId, + }) + .from(schema.githubInstallationTable) + .where( + eq( + schema.githubInstallationTable.installationId, + input.installationId + ) + ) + .limit(1) + .pipe( + Effect.mapError(() => + providerFailure("installation lookup") + ) + ); + const reusedConnectionId = + existingInstallation === undefined || + existingInstallation.connectionId === connection.id + ? undefined + : existingInstallation.connectionId; + if (reusedConnectionId !== undefined) { + const [reusedConnection] = yield* db + .select() + .from(schema.integrationConnectionTable) + .where( + and( + eq( + schema.integrationConnectionTable.id, + reusedConnectionId + ), + eq( + schema.integrationConnectionTable.organizationId, + parsed.organizationId + ), + eq( + schema.integrationConnectionTable.provider, + githubProviderKey + ) + ) + ) + .limit(1) + .for("update") + .pipe( + Effect.mapError(() => + providerFailure("installation connection lookup") + ) + ); + if ( + reusedConnection === undefined || + reusedConnection.lifecycle === "connecting" + ) { + return yield* providerFailure( + "installation state validation" + ); + } + yield* db + .update(schema.integrationConnectionTable) + .set({ + archivedAt: null, + credentialsCiphertext: null, + lifecycle: + installation.suspended_at === null + ? "active" + : "paused", + remoteAccountId: installationAccount.login, + retentionExpiresAt: null, + safeDisplayMetadata: { + login: installationAccount.login, + }, + updatedAt: new Date(), + }) + .where( + eq( + schema.integrationConnectionTable.id, + reusedConnection.id + ) + ) + .pipe( + Effect.mapError(() => + providerFailure("connection reactivation") + ) + ); + yield* db + .delete(schema.integrationConnectionTable) + .where( + eq(schema.integrationConnectionTable.id, connection.id) + ) + .pipe( + Effect.mapError(() => + providerFailure("installation connection cleanup") + ) + ); + yield* db + .insert(schema.githubInstallationTable) + .values({ + connectionId: reusedConnection.id, + installationId: input.installationId, + accountId: String(installationAccount.id), + accountLogin: installationAccount.login, + accountType: installationAccount.type, + suspendedAt: installation.suspended_at, + }) + .onConflictDoUpdate({ + target: schema.githubInstallationTable.connectionId, + set: { + installationId: input.installationId, + accountId: String(installationAccount.id), + accountLogin: installationAccount.login, + accountType: installationAccount.type, + suspendedAt: installation.suspended_at, + }, + }) + .pipe( + Effect.mapError(() => + providerFailure("installation persistence") + ) + ); + return; + } + const activated = yield* db + .update(schema.integrationConnectionTable) + .set({ + credentialsCiphertext: null, + lifecycle: + installation.suspended_at === null ? "active" : "paused", + remoteAccountId: installationAccount.login, + safeDisplayMetadata: { login: installationAccount.login }, + }) + .where( + and( + eq(schema.integrationConnectionTable.id, connection.id), + eq( + schema.integrationConnectionTable.lifecycle, + "connecting" + ), + eq( + schema.integrationConnectionTable.credentialsCiphertext, + credentialsCiphertext + ) + ) + ) + .returning({ id: schema.integrationConnectionTable.id }) + .pipe( + Effect.mapError(() => + providerFailure("connection activation") + ) + ); + if (activated.length === 0) { + return yield* providerFailure( + "installation state validation" + ); + } + yield* db + .insert(schema.githubInstallationTable) + .values({ + connectionId: connection.id, + installationId: input.installationId, + accountId: String(installationAccount.id), + accountLogin: installationAccount.login, + accountType: installationAccount.type, + suspendedAt: installation.suspended_at, + }) + .onConflictDoUpdate({ + target: schema.githubInstallationTable.connectionId, + set: { + installationId: input.installationId, + accountId: String(installationAccount.id), + accountLogin: installationAccount.login, + accountType: installationAccount.type, + suspendedAt: installation.suspended_at, + }, + }) + .pipe( + Effect.mapError(() => + providerFailure("installation persistence") + ) + ); + }) + ) + .pipe( + Effect.mapError(() => + providerFailure("installation activation transaction") + ) + ); + return { organizationId: connection.organizationId }; + }), + listRepositories: ({ connectionId }) => + Effect.gen(function* () { + const accessToken = + yield* installationTokenForConnection(connectionId); + const repositories: Array<{ + readonly fullName: string; + readonly name: string; + readonly owner: string; + readonly private: boolean; + }> = []; + const maximumRepositoryPages = 100; + for (let page = 1; page <= maximumRepositoryPages; page += 1) { + const result = yield* api + .listInstallationRepositories({ accessToken, page }) + .pipe( + Effect.mapError(() => providerFailure("repository listing")) + ); + repositories.push( + ...result.repositories.map((repository) => ({ + fullName: repository.full_name, + name: repository.name, + owner: repository.owner.login, + private: repository.private, + })) + ); + if (result.repositories.length === 0) { + return repositories; + } + if ( + repositories.length >= result.total_count || + result.repositories.length < 100 + ) { + return repositories; + } + } + return repositories; + }).pipe( + // installationTokenForConnection keeps its typed failures so a + // missing installation stays NotFound and token auth failures map + // through issueFailure like every other GitHub RPC. + Effect.mapError(issueFailure("repository listing")) + ), + uninstallInstallation: ({ connectionId }) => + Effect.gen(function* () { + const installationId = + yield* installationIdForConnection(connectionId); + const appJwt = yield* createGitHubAppJwt({ + appId: config.githubAppId ?? "", + now: new Date(), + privateKey: config.githubPrivateKey, + }).pipe(Effect.mapError(() => providerFailure("App authentication"))); + yield* api + .deleteInstallation({ appJwt, installationId }) + .pipe( + Effect.mapError(() => providerFailure("installation removal")) + ); + }), + createIssue: (input) => + installationTokenForConnection(input.connectionId).pipe( + Effect.flatMap((accessToken) => + Effect.gen(function* () { + const issue = yield* api.createIssue({ + accessToken, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + title: renderGitHubIssueTitle({ title: input.postTitle }), + body: renderGitHubIssueBody({ + description: input.postDescription, + postUrl: input.postUrl.toString(), + }), + }); + // The Feeblo backlink lives in a bot comment, matching the + // link-existing-issue flow and the automatic delivery path. + yield* api.createIssueBacklinkComment({ + accessToken, + backlinkUrl: input.postUrl, + issueNumber: issue.number, + repositoryName: input.repositoryName, + repositoryOwner: input.repositoryOwner, + }); + return issue; + }) + ), + Effect.map((issue) => ({ + connectionId: input.connectionId, + remoteId: issue.node_id, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + issueNumber: issue.number, + issueUrl: issue.html_url, + issueState: issue.state, + title: issue.title, + })), + Effect.mapError(issueFailure("issue creation")) + ), + resolveIssue: (input) => + installationTokenForConnection(input.connectionId).pipe( + Effect.flatMap((accessToken) => + Effect.gen(function* () { + const issue = yield* api.getIssue({ + accessToken, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + issueNumber: input.issueNumber, + }); + const existingLink = yield* db + .select({ id: schema.postExternalResourceLinkTable.id }) + .from(schema.postExternalResourceLinkTable) + .innerJoin( + schema.integrationExternalResourceTable, + eq( + schema.integrationExternalResourceTable.id, + schema.postExternalResourceLinkTable.externalResourceId + ) + ) + .where( + and( + eq( + schema.postExternalResourceLinkTable.postId, + input.postId + ), + eq( + schema.integrationExternalResourceTable.connectionId, + input.connectionId + ), + ...gitHubIssueSafeMetadataConditions({ + issueNumber: input.issueNumber, + repositoryName: input.repositoryName, + repositoryOwner: input.repositoryOwner, + }) + ) + ) + .limit(1) + .pipe( + Effect.mapError(() => providerFailure("issue link lookup")) + ); + if (existingLink.length === 0) { + yield* api.createIssueBacklinkComment({ + accessToken, + backlinkUrl: input.postUrl, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + issueNumber: input.issueNumber, + }); + } + return issue; + }) + ), + Effect.map((issue) => ({ + connectionId: input.connectionId, + remoteId: issue.node_id, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + issueNumber: issue.number, + issueUrl: issue.html_url, + issueState: issue.state, + title: issue.title, + })), + Effect.mapError(issueFailure("issue linking")) + ), + }); + return provider; + }) +); diff --git a/apps/server/src/github.ts b/apps/server/src/github.ts new file mode 100644 index 00000000..6d396d91 --- /dev/null +++ b/apps/server/src/github.ts @@ -0,0 +1,197 @@ +import { Database } from "@feeblo/db"; +import { GitHubInboundService } from "@feeblo/domain/integration/github/inbound-service"; +import { GitHubManagementService } from "@feeblo/domain/integration/github/management-service"; +import { parseGitHubAppInstallationCallbackUrl } from "@feeblo/domain/integration/github/oauth-callback"; +import type { IntegrationProviderRegistry } from "@feeblo/integration-core"; +import { ParsedGitHubInboundRequest } from "@feeblo/integration-github/inbound-schema"; +import { + githubIssueWebhookCapabilityKey, + githubProviderKey, +} from "@feeblo/integration-github/manifest"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as HttpHeaders from "effect/unstable/http/Headers"; +import * as HttpRouter from "effect/unstable/http/HttpRouter"; +import type * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; +import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; +import { ServerConfig } from "./config"; + +const headerValue = ( + request: HttpServerRequest.HttpServerRequest, + name: string +): string | undefined => + Option.getOrUndefined(HttpHeaders.get(request.headers, name)); + +const settingsRedirect = ( + appUrl: string, + status: "connected" | "error", + message: string, + organizationId?: string +): string => { + const base = + organizationId === undefined + ? `${appUrl}/settings/integrations` + : `${appUrl}/${organizationId}/settings/integrations`; + return `${base}?github=${status}&message=${encodeURIComponent(message)}`; +}; + +/** GitHub redirects here after its App installer authorizes Feeblo to verify ownership. */ +export const makeGitHubAppInstallationCallbackRouter = () => + HttpRouter.use((router) => + router.add( + "GET", + "/github/app/installations/callback", + (request: HttpServerRequest.HttpServerRequest) => + Effect.gen(function* () { + const config = yield* ServerConfig; + const parsed = yield* Effect.exit( + parseGitHubAppInstallationCallbackUrl(request.url) + ); + if (Exit.isFailure(parsed)) { + yield* Effect.logError(parsed.cause); + return HttpServerResponse.redirect( + settingsRedirect( + config.appUrl, + "error", + "GitHub App installation failed." + ) + ); + } + const management = yield* GitHubManagementService; + const completed = yield* Effect.exit( + management.connectComplete(parsed.value) + ); + if (Exit.isFailure(completed)) { + yield* Effect.logError(completed.cause); + return HttpServerResponse.redirect( + settingsRedirect( + config.appUrl, + "error", + "GitHub App installation failed." + ) + ); + } + return HttpServerResponse.redirect( + settingsRedirect( + config.appUrl, + "connected", + "Feeblo is now connected to GitHub.", + completed.value.organizationId + ) + ); + }) + ) + ).pipe(Layer.provide(Database.DatabaseContextLive), Layer.orDie); + +/** One global GitHub App webhook endpoint. Verified payload installation IDs select the owning connection. */ +const makeGitHubAppWebhookRouter = (registry: IntegrationProviderRegistry) => + HttpRouter.use((router) => + router.add( + "POST", + "/github/app/webhooks", + (request: HttpServerRequest.HttpServerRequest) => + Effect.gen(function* () { + const inboundHandler = registry.getInboundHandler({ + capabilityKey: githubIssueWebhookCapabilityKey, + provider: githubProviderKey, + }); + if (inboundHandler === undefined) { + return HttpServerResponse.text("not found", { status: 404 }); + } + const response = yield* inboundHandler.handle({ + headers: { + "x-github-delivery": headerValue(request, "x-github-delivery"), + "x-github-event": headerValue(request, "x-github-event"), + "x-hub-signature-256": headerValue( + request, + "x-hub-signature-256" + ), + }, + rawBody: yield* request.text, + }); + if (response.status !== 200) { + return HttpServerResponse.text(String(response.body), { + status: response.status, + }); + } + const parsed = yield* Effect.exit( + Schema.decodeUnknownEffect( + Schema.toType(ParsedGitHubInboundRequest) + )(response.body) + ); + if (Exit.isFailure(parsed)) { + return HttpServerResponse.text("invalid request payload", { + status: 400, + }); + } + const inbound = yield* GitHubInboundService; + switch (parsed.value.kind) { + case "issue": { + const payload = parsed.value.payload; + if ( + payload.action !== "opened" && + payload.action !== "reopened" && + payload.action !== "closed" + ) { + break; + } + yield* inbound.applyIssueWebhook({ + deliveryId: parsed.value.deliveryId, + eventName: "issues", + installationId: String(payload.installation.id), + issueNumber: payload.issue.number, + issueState: payload.issue.state, + repositoryName: payload.repository.name, + repositoryOwner: payload.repository.owner.login, + }); + break; + } + case "installation": { + const action = parsed.value.payload.action; + if ( + action === "deleted" || + action === "suspend" || + action === "unsuspend" + ) { + yield* inbound.applyInstallationLifecycleWebhook({ + action, + deliveryId: parsed.value.deliveryId, + installationId: String(parsed.value.payload.installation.id), + }); + } + break; + } + case "installation_repositories": + // Settings validate repository availability on update; this event is + // acknowledged so GitHub does not retry a delivery with no mutation. + break; + default: + return HttpServerResponse.text("unsupported GitHub App webhook", { + status: 202, + }); + } + return HttpServerResponse.empty({ status: 202 }); + }).pipe( + Effect.catch((cause) => + Effect.logError(cause).pipe( + Effect.as( + HttpServerResponse.text( + "GitHub App webhook processing failed", + { status: 500 } + ) + ) + ) + ) + ) + ) + ); + +/** Server HTTP adapters for GitHub App setup and its global webhook endpoint. */ +export const makeGitHubRouters = (registry: IntegrationProviderRegistry) => + Layer.mergeAll( + makeGitHubAppInstallationCallbackRouter(), + makeGitHubAppWebhookRouter(registry) + ).pipe(Layer.orDie); diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 47cf8e0a..470f0800 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -31,6 +31,10 @@ import { DiscordManagementServiceLive, DiscordUserServiceLive, } from "@feeblo/domain/integration/discord"; +import { ExternalResourceServiceLive } from "@feeblo/domain/integration/external-resource/live"; +import { GitHubIntegrationConfig } from "@feeblo/domain/integration/github/config"; +import { GitHubInboundServiceLive } from "@feeblo/domain/integration/github/inbound-live"; +import { GitHubManagementServiceLive } from "@feeblo/domain/integration/github/management-live"; import { SlackFeedbackServiceLive, SlackInboundServiceLive, @@ -38,6 +42,7 @@ import { SlackManagementServiceLive, SlackUserServiceLive, } from "@feeblo/domain/integration/slack"; +import { NotificationService } from "@feeblo/domain/notification/service"; import { handleOgImage } from "@feeblo/domain/og-image/handler"; import { OgImageService } from "@feeblo/domain/og-image/service"; import { PostRepository } from "@feeblo/domain/post/repository"; @@ -63,6 +68,7 @@ import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Logger from "effect/Logger"; import * as Option from "effect/Option"; +import * as Redacted from "effect/Redacted"; import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Tracer from "effect/Tracer"; @@ -77,6 +83,8 @@ import { ServerConfig } from "./config"; import { makeDiscordRouters } from "./discord"; import { e2eRoadmapSeedRouter } from "./e2e-roadmap-seed"; import { e2eSetPlanRouter } from "./e2e-set-plan"; +import { makeGitHubRouters } from "./github"; +import { GitHubProviderLive } from "./github-provider"; import { makeIntegrationLayers } from "./integrations"; import { makeSlackRouters } from "./slack"; @@ -300,10 +308,25 @@ const program = Effect.gen(function* () { initAuthHandler(makeMailerLayer, RateLimitLayer) ); const integrationRuntime = yield* makeIntegrationLayers.pipe( - Effect.provideService(ServerConfig, config) + Effect.provideService(ServerConfig, config), + Effect.provide(ExternalResourceServiceLive) ); const SlackRouters = makeSlackRouters(integrationRuntime.registry); const DiscordRouters = makeDiscordRouters(integrationRuntime.registry); + const GitHubRouters = makeGitHubRouters(integrationRuntime.registry); + const GitHubConfigLayer = Layer.succeed( + GitHubIntegrationConfig, + GitHubIntegrationConfig.of({ + clientId: config.githubClientId ?? "", + configured: + config.githubAppId !== undefined && + config.githubAppSlug !== undefined && + config.githubClientId !== undefined && + Redacted.value(config.githubClientSecret) !== "" && + Redacted.value(config.githubPrivateKey) !== "" && + Redacted.value(config.githubWebhookSecret) !== "", + }) + ); const ServiceLayers = Layer.mergeAll( WorkFlowLayer, SiteRepository.layer, @@ -312,6 +335,7 @@ const program = Effect.gen(function* () { EmailProviderFeedbackService.layer, EmailSubscriptionRepository.layer, integrationRuntime.layer, + ExternalResourceServiceLive, SlackManagementServiceLive.pipe( Layer.provide(SlackIntegrationConfig.layer), Layer.provide(Database.DatabaseContextLive) @@ -343,6 +367,26 @@ const program = Effect.gen(function* () { Layer.provide(PostSubscriptionRepository.layer), Layer.provide(Database.DatabaseContextLive) ), + GitHubManagementServiceLive.pipe( + Layer.provide(ExternalResourceServiceLive), + Layer.provide( + GitHubProviderLive.pipe( + Layer.provide(GitHubConfigLayer), + Layer.provide(Layer.succeed(ServerConfig, config)), + Layer.provide(Database.DatabaseContextLive) + ) + ), + Layer.provide(GitHubConfigLayer), + Layer.provide(EmailOutboxConfig.layer), + Layer.provide(Database.DatabaseContextLive) + ), + GitHubInboundServiceLive.pipe( + Layer.provide(NotificationService.layer), + Layer.provide(IntegrationEventRecorderLive), + Layer.provide(PostRepository.layer), + Layer.provide(EmailOutboxConfig.layer), + Layer.provide(Database.DatabaseContextLive) + ), EntitlementPolicy.layer.pipe(Layer.provide(WorkspaceRepository.layer)) ).pipe(Layer.provideMerge(Database.DatabaseContextLive)); const RootRouterLive: Layer.Layer = @@ -410,7 +454,8 @@ const program = Effect.gen(function* () { BetterAuthRouterLive, DocsRoute, SlackRouters, - DiscordRouters + DiscordRouters, + GitHubRouters ); const AllRoutes = MergedRoutes.pipe( Layer.provide( diff --git a/apps/server/src/integrations.ts b/apps/server/src/integrations.ts index 20b7e0a8..45a4047a 100644 --- a/apps/server/src/integrations.ts +++ b/apps/server/src/integrations.ts @@ -1,11 +1,13 @@ import { currentDb, type Database, schema } from "@feeblo/db"; import { WebhookIntegrationConfig } from "@feeblo/domain/integration/config"; import { DiscordIntegrationConfig } from "@feeblo/domain/integration/discord/config"; +import { ExternalResourceService } from "@feeblo/domain/integration/external-resource/service"; import { SlackIntegrationConfig } from "@feeblo/domain/integration/slack/config"; import { WebhookManagementServiceLive } from "@feeblo/domain/integration/webhook-management-live"; import type { WebhookManagementService } from "@feeblo/domain/integration/webhook-management-service"; import { InternalServerError } from "@feeblo/domain/rpc-errors"; import { + IntegrationDeliveryWorkerPersistenceError, type IntegrationEventRecorder, IntegrationEventRecorderLive, IntegrationProviderInvalidConfigurationError, @@ -21,6 +23,13 @@ import { makeDiscordCredentialResolver, makeDiscordProviderRegistration, } from "@feeblo/integration-discord"; +import { + makeGitHubApiClient, + makeGitHubCredentialResolver, + makeGitHubInstallationTokenResolver, + makeGitHubProviderRegistration, +} from "@feeblo/integration-github"; +import { githubProviderKey } from "@feeblo/integration-github/manifest"; import { makeSlackCredentialResolver, makeSlackProviderRegistration, @@ -36,6 +45,7 @@ import * as Crypto from "effect/Crypto"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Redacted from "effect/Redacted"; import * as Schedule from "effect/Schedule"; import { ServerConfig } from "./config"; @@ -63,6 +73,7 @@ export const makeIntegrationLayers: Effect.Effect< IntegrationProviderRegistryValidationError | InternalServerError, | ServerConfig | Database.Database + | ExternalResourceService | WebhookIntegrationConfig | SlackIntegrationConfig | DiscordIntegrationConfig @@ -70,6 +81,7 @@ export const makeIntegrationLayers: Effect.Effect< > = Effect.gen(function* () { const config = yield* ServerConfig; const db = yield* currentDb; + const externalResources = yield* ExternalResourceService; const { encryptionKey, endpointSecurityPolicy } = yield* WebhookIntegrationConfig; @@ -168,23 +180,97 @@ export const makeIntegrationLayers: Effect.Effect< credentialResolver: discordCredentialResolver, publicKey: discordPublicKey, }); + const githubConfigured = + config.githubAppId !== undefined && + Redacted.value(config.githubPrivateKey) !== "" && + Redacted.value(config.githubWebhookSecret) !== ""; + const githubApi = makeGitHubApiClient(); + const githubInstallationTokens = yield* makeGitHubInstallationTokenResolver({ + apiClient: githubApi, + appId: config.githubAppId ?? "", + privateKey: config.githubPrivateKey, + }); + const githubCredentialResolver = makeGitHubCredentialResolver({ + installationTokenResolver: githubInstallationTokens, + loadInstallationId: (input) => + Effect.gen(function* () { + const [connection] = yield* db + .select({ + installationId: schema.githubInstallationTable.installationId, + }) + .from(schema.githubInstallationTable) + .where( + eq(schema.githubInstallationTable.connectionId, input.connection.id) + ) + .limit(1) + .pipe( + Effect.mapError( + () => + new IntegrationProviderTemporaryFailure({ + message: "GitHub credentials could not be loaded", + provider: githubProviderKey, + }) + ) + ); + return connection?.installationId ?? null; + }), + }); + const githubRegistration = makeGitHubProviderRegistration({ + apiClient: githubApi, + credentialResolver: githubCredentialResolver, + webhookSecret: config.githubWebhookSecret, + }); // Providers are only exposed when their credentials are configured; // otherwise the server runs with the remaining providers only. const registry = yield* makeIntegrationProviderRegistry([ registration, ...(slackConfigured ? [slackRegistration] : []), ...(discordConfigured ? [discordRegistration] : []), + ...(githubConfigured ? [githubRegistration] : []), ]); - // Deliveries are claimed only for capability keys the startup-validated - // registry actually exposes; the kernel never hardcodes a provider capability. - const claimableCapabilityKeys = registry.manifests.flatMap((manifest) => - manifest.capabilities - .filter((capability) => capability.direction === "outbound") - .map((capability) => capability.key) - ); + // Deliveries are claimed only for capability keys owned by the matching + // provider; the startup-validated registry supplies each provider's outbound + // capabilities, so cross-provider or unknown keys are never claimable. + const claimableCapabilityKeysByProvider = new Map< + string, + readonly string[] + >(); + for (const manifest of registry.manifests) { + claimableCapabilityKeysByProvider.set( + manifest.provider, + manifest.capabilities + .filter((capability) => capability.direction === "outbound") + .map((capability) => capability.key) + ); + } const workerRepository = yield* makeIntegrationDeliveryWorkerRepository( - claimableCapabilityKeys + claimableCapabilityKeysByProvider, + ({ connection, drafts, event }) => + Effect.forEach(drafts, (draft) => + externalResources.recordPostLink({ + postId: draft.postId, + resource: { + connectionId: connection.id, + displayKey: draft.displayKey ?? null, + organizationId: event.organizationId, + remoteId: draft.remoteId, + remoteUrl: draft.remoteUrl, + resourceType: draft.resourceType, + safeMetadata: draft.safeMetadata, + stateKey: draft.stateKey ?? null, + title: draft.title ?? null, + }, + }) + ).pipe( + Effect.asVoid, + Effect.mapError( + () => + new IntegrationDeliveryWorkerPersistenceError({ + operation: "record_external_resource_drafts", + }) + ) + ) ); const lifecycleRepository = yield* makeIntegrationManagementRepository; const crypto = yield* Crypto.Crypto; diff --git a/apps/server/src/slack.ts b/apps/server/src/slack.ts index a1dfce80..78c111ab 100644 --- a/apps/server/src/slack.ts +++ b/apps/server/src/slack.ts @@ -7,7 +7,11 @@ import { } from "@feeblo/domain/integration/slack"; import type { IntegrationProviderRegistry } from "@feeblo/integration-core"; import type { ParsedSlackInboundRequest } from "@feeblo/integration-slack/inbound-schema"; -import { slackProviderKey } from "@feeblo/integration-slack/manifest"; +import { + slackCommandsCapabilityKey, + slackMessageActionCapabilityKey, + slackProviderKey, +} from "@feeblo/integration-slack/manifest"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Layer from "effect/Layer"; @@ -45,7 +49,9 @@ const isParsedSlackInboundRequest = ( const handleInbound = ( request: HttpServerRequest.HttpServerRequest, - capabilityKey: "commands" | "message.action", + capabilityKey: + | typeof slackCommandsCapabilityKey + | typeof slackMessageActionCapabilityKey, registry: IntegrationProviderRegistry ) => Effect.gen(function* () { @@ -171,7 +177,9 @@ const makeSlackCommandRouter = (registry: IntegrationProviderRegistry) => "POST", "/slack/commands/feeblo", (request: HttpServerRequest.HttpServerRequest) => - handleInbound(request, "commands", registry).pipe(Effect.orDie) + handleInbound(request, slackCommandsCapabilityKey, registry).pipe( + Effect.orDie + ) ) ); @@ -182,7 +190,9 @@ const makeSlackInteractiveRouter = (registry: IntegrationProviderRegistry) => "POST", "/slack/interactive", (request: HttpServerRequest.HttpServerRequest) => - handleInbound(request, "message.action", registry).pipe(Effect.orDie) + handleInbound(request, slackMessageActionCapabilityKey, registry).pipe( + Effect.orDie + ) ) ); diff --git a/apps/server/vitest.config.ts b/apps/server/vitest.config.ts new file mode 100644 index 00000000..7f9a503c --- /dev/null +++ b/apps/server/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts"], + pool: "threads", + }, +}); diff --git a/apps/web/package.json b/apps/web/package.json index ee180602..f5c27f63 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -38,7 +38,7 @@ "@feeblo/ui": "workspace:*", "@feeblo/utils": "workspace:*", "@feeblo/web-shared": "workspace:*", - "@effect/atom-react": "4.0.0-beta.66", + "@effect/atom-react": "catalog:", "@floating-ui/dom": "catalog:", "@hugeicons/core-free-icons": "^4.2.3", "@hugeicons/react": "^1.1.9", diff --git a/apps/web/src/dashboard/features/github/atoms.ts b/apps/web/src/dashboard/features/github/atoms.ts new file mode 100644 index 00000000..b2dac403 --- /dev/null +++ b/apps/web/src/dashboard/features/github/atoms.ts @@ -0,0 +1,127 @@ +import type { GitHubSyncRule as GitHubSyncRuleSchema } from "@feeblo/domain/integration/github/schema"; +import * as Effect from "effect/Effect"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import * as AtomRegistry from "effect/unstable/reactivity/AtomRegistry"; +import { + loadGitHubBoards, + loadGitHubConnections, + loadGitHubIntegrationStatus, + loadGitHubPostStatuses, + loadGitHubPublishSettings, + loadGitHubRepositories, + loadGitHubSyncRules, +} from "./lib/github-connections"; + +export type GitHubConnection = Awaited< + ReturnType +>[number]; +export type GitHubRepository = Awaited< + ReturnType +>[number]; +export type GitHubPublishSettings = Awaited< + ReturnType +>; +/** GitHub state synchronization rule derived from the shared Effect Schema. */ +export type GitHubSyncRule = GitHubSyncRuleSchema; +export type GitHubBoard = Awaited>[number]; +export type GitHubPostStatus = Awaited< + ReturnType +>[number]; + +/** Isolated Effect atom registry for GitHub integration screens. */ +export const gitHubAtomRegistry = AtomRegistry.make(); + +/** GitHub App installations cached per organization. */ +export const gitHubConnectionsAtom = Atom.family((organizationId: string) => + Atom.make( + Effect.tryPromise(() => loadGitHubConnections(organizationId)) + ).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); + +/** GitHub setup availability for this deployment. */ +export const gitHubIntegrationStatusAtom = Atom.make( + Effect.tryPromise(() => loadGitHubIntegrationStatus()) +).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") +); + +export type GitHubConnectionArgs = { + readonly organizationId: string; + readonly connectionId: string; +}; + +/** Repositories available to one GitHub connection. */ +export const gitHubRepositoriesAtom = Atom.family( + (args: GitHubConnectionArgs) => + Atom.make(Effect.tryPromise(() => loadGitHubRepositories(args))).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); + +/** Automatic publishing settings for one GitHub connection. */ +export const gitHubPublishSettingsAtom = Atom.family( + (args: GitHubConnectionArgs) => + Atom.make(Effect.tryPromise(() => loadGitHubPublishSettings(args))).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); + +/** State synchronization rules for one GitHub connection. */ +export const gitHubSyncRulesAtom = Atom.family((args: GitHubConnectionArgs) => + Atom.make(Effect.tryPromise(() => loadGitHubSyncRules(args))).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); + +/** Boards available for scoping automatic GitHub issue publishing. */ +export const gitHubBoardsAtom = Atom.family((organizationId: string) => + Atom.make(Effect.tryPromise(() => loadGitHubBoards(organizationId))).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); + +/** Feeblo statuses available as synchronization-rule targets. */ +export const gitHubPostStatusesAtom = Atom.family((organizationId: string) => + Atom.make( + Effect.tryPromise(() => loadGitHubPostStatuses(organizationId)) + ).pipe( + Atom.swr({ + staleTime: "30 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); diff --git a/apps/web/src/dashboard/features/github/components/github-settings.tsx b/apps/web/src/dashboard/features/github/components/github-settings.tsx new file mode 100644 index 00000000..0fe1b108 --- /dev/null +++ b/apps/web/src/dashboard/features/github/components/github-settings.tsx @@ -0,0 +1,865 @@ +import { + RegistryContext, + useAtomRefresh, + useAtomValue, +} from "@effect/atom-react"; +import { BoardId, PostStatusId } from "@feeblo/id"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogPopup, + AlertDialogTitle, + AlertDialogTrigger, +} from "@feeblo/ui/alert-dialog"; +import { Button } from "@feeblo/ui/button"; +import { Card, CardPanel } from "@feeblo/ui/card"; +import { + Frame, + FrameDescription, + FrameHeader, + FramePanel, + FrameTitle, +} from "@feeblo/ui/frame"; +import { + Select, + SelectItem, + SelectPopup, + SelectTrigger, + SelectValue, +} from "@feeblo/ui/select"; +import { Switch } from "@feeblo/ui/switch"; +import { toastManager } from "@feeblo/ui/toast"; +import { Delete02Icon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { + startTransition, + useEffect, + useOptimistic, + useRef, + useState, +} from "react"; +import { + type GitHubConnection, + type GitHubPostStatus, + type GitHubSyncRule, + gitHubAtomRegistry, + gitHubBoardsAtom, + gitHubConnectionsAtom, + gitHubIntegrationStatusAtom, + gitHubPostStatusesAtom, + gitHubPublishSettingsAtom, + gitHubRepositoriesAtom, + gitHubSyncRulesAtom, +} from "../atoms"; +import { + createGitHubSyncRule, + deleteGitHubSyncRule, + disconnectGitHubConnection, + startGitHubConnect, + updateGitHubPublishSettings, + updateGitHubSyncRule, +} from "../lib/github-connections"; + +type AsyncListState = { + readonly list: readonly T[]; + readonly isLoading: boolean; + readonly loadFailed: boolean; +}; + +function useAsyncList( + result: AsyncResult.AsyncResult +): AsyncListState { + return AsyncResult.match(result, { + onInitial: () => ({ list: [], isLoading: true, loadFailed: false }), + onFailure: ({ previousSuccess }) => + Option.match(previousSuccess, { + onNone: () => ({ list: [], isLoading: false, loadFailed: true }), + onSome: ({ value }) => ({ + list: value, + isLoading: false, + loadFailed: false, + }), + }), + onSuccess: ({ value }) => ({ + list: value, + isLoading: false, + loadFailed: false, + }), + }); +} + +/** GitHub connection, automatic publishing, and issue-state rule settings. */ +export function GitHubSettings({ + organizationId, +}: { + readonly organizationId: string; +}) { + return ( + + + + ); +} + +function GitHubSettingsContent({ + organizationId, +}: { + readonly organizationId: string; +}) { + const [connecting, setConnecting] = useState(false); + const statusResult = useAtomValue(gitHubIntegrationStatusAtom); + const connectionsResult = useAtomValue(gitHubConnectionsAtom(organizationId)); + const refreshConnections = useAtomRefresh( + gitHubConnectionsAtom(organizationId) + ); + const configured = AsyncResult.match(statusResult, { + onInitial: () => null as boolean | null, + onFailure: () => false, + onSuccess: ({ value }) => value, + }); + const connections = useAsyncList(connectionsResult); + + const installGitHubApp = async () => { + setConnecting(true); + try { + const { authorizeUrl } = await startGitHubConnect(organizationId); + window.location.assign(authorizeUrl.toString()); + } catch { + setConnecting(false); + toastManager.add({ + title: "Could not start GitHub App installation", + type: "error", + }); + } + }; + + if (configured === null) { + return ; + } + if (!configured) { + return ( + + ); + } + if (connections.isLoading) { + return ; + } + if (connections.loadFailed) { + return ( + + ); + } + if (connections.list.length === 0) { + return ( + + + GitHub + + Let the Feeblo bot create GitHub issues and comments, then keep post + status in sync. + + + +

Install the GitHub App

+

+ Choose the GitHub organization and repositories where the Feeblo bot + can publish issues, add linked-feedback comments, and synchronize + issue states. +

+
+ +
+
+ + ); + } + return ( +
+ {connections.list.map((connection) => ( + { + refreshConnections(); + toastManager.add({ + title: "GitHub integration removed", + type: "success", + }); + }} + organizationId={organizationId} + /> + ))} +
+ ); +} + +function LoadingCard({ message }: { readonly message: string }) { + return ( + + +

{message}

+
+
+ ); +} + +function RetryCard({ + message, + onRetry, +}: { + readonly message: string; + readonly onRetry: () => void; +}) { + return ( + + +
+ {message} + +
+
+
+ ); +} + +function GitHubConnectionFrame({ + connection, + onDisconnected, + organizationId, +}: { + readonly connection: GitHubConnection; + readonly onDisconnected: () => void; + readonly organizationId: string; +}) { + const args = { organizationId, connectionId: connection.id }; + const [dialogOpen, setDialogOpen] = useState(false); + const [disconnecting, setDisconnecting] = useState(false); + + const handleDisconnect = async () => { + setDisconnecting(true); + try { + await disconnectGitHubConnection(args); + setDialogOpen(false); + onDisconnected(); + } catch { + setDisconnecting(false); + setDialogOpen(false); + toastManager.add({ + title: "Could not remove GitHub integration", + type: "error", + }); + } + }; + + return ( + + + {connection.login ?? "GitHub installation"} + + Feeblo GitHub App installed for this account. + + + {connection.lifecycle === "active" ? ( + <> + + + + ) : ( + +

+ Finishing GitHub App installation. Refresh this page after selecting + repositories in GitHub. +

+
+ )} + +

Remove integration

+

+ Stop GitHub publishing and synchronization for this Feeblo + organization. +

+
+ { + if (!disconnecting) { + setDialogOpen(open); + } + }} + open={dialogOpen} + > + + Remove GitHub integration + + } + /> + + + Remove GitHub integration? + + Feeblo will stop creating issues and processing issue status + changes for {connection.login ?? "this GitHub installation"}. + Existing post-to-issue links will remain. The Feeblo bot will + also be uninstalled from this GitHub account and lose access + to its selected repositories. + + + + Cancel + + {disconnecting ? "Removing…" : "Remove integration"} + + + + +
+
+ + ); +} + +function GitHubPublishingSettings({ + args, +}: { + readonly args: { + readonly organizationId: string; + readonly connectionId: string; + }; +}) { + const settingsResult = useAtomValue(gitHubPublishSettingsAtom(args)); + const repositoriesResult = useAtomValue(gitHubRepositoriesAtom(args)); + const boardsResult = useAtomValue(gitHubBoardsAtom(args.organizationId)); + const refreshSettings = useAtomRefresh(gitHubPublishSettingsAtom(args)); + const settings = AsyncResult.match(settingsResult, { + onInitial: () => null, + onFailure: ({ previousSuccess }) => + Option.getOrNull(previousSuccess)?.value ?? null, + onSuccess: ({ value }) => value, + }); + const repositories = useAsyncList(repositoriesResult); + const boards = useAsyncList(boardsResult); + const [optimisticSettings, setOptimisticSettings] = useOptimistic( + settings, + (_current, next: NonNullable) => next + ); + + const save = (next: NonNullable) => { + startTransition(async () => { + setOptimisticSettings(next); + try { + await updateGitHubPublishSettings({ ...args, ...next }); + refreshSettings(); + } catch { + refreshSettings(); + toastManager.add({ + title: "Could not save GitHub publishing settings", + type: "error", + }); + } + }); + }; + if (!optimisticSettings) { + return ( + +

+ Loading issue publishing settings… +

+
+ ); + } + const selectedRepository = + optimisticSettings.repositoryOwner && optimisticSettings.repositoryName + ? `${optimisticSettings.repositoryOwner}/${optimisticSettings.repositoryName}` + : ""; + return ( + +

Automatic issue publishing

+

+ Create a GitHub issue automatically when a new post matches this scope. +

+
+
+ Publish new posts automatically + + save({ ...optimisticSettings, enabled }) + } + /> +
+
+ Repository + +
+
+ Boards +
+ { + if (checked) { + save({ + ...optimisticSettings, + boardScope: "any_board", + boardId: null, + }); + } + }} + /> + {boards.list.map((board) => ( + { + if (checked) { + Option.match( + Schema.decodeUnknownOption(BoardId.schema)(board.id), + { + onNone: () => + toastManager.add({ + title: "The selected board is no longer available", + type: "error", + }), + onSome: (boardId) => + save({ + ...optimisticSettings, + boardScope: "specific_board", + boardId, + }), + } + ); + } + }} + /> + ))} + {boards.isLoading ? ( +

Loading boards…

+ ) : null} + {boards.loadFailed ? ( +

+ Boards could not be loaded. +

+ ) : null} +
+
+
+
+ ); +} + +function BoardScopeSwitch({ + checked, + description, + label, + onCheckedChange, +}: { + readonly checked: boolean; + readonly description: string; + readonly label: string; + readonly onCheckedChange: (checked: boolean) => void; +}) { + return ( +
+
+

{label}

+

{description}

+
+ +
+ ); +} + +function GitHubSyncRules({ + args, +}: { + readonly args: { + readonly organizationId: string; + readonly connectionId: string; + }; +}) { + const rulesResult = useAtomValue(gitHubSyncRulesAtom(args)); + const statusesResult = useAtomValue( + gitHubPostStatusesAtom(args.organizationId) + ); + const refreshRules = useAtomRefresh(gitHubSyncRulesAtom(args)); + const rules = useAsyncList(rulesResult); + const statuses = useAsyncList(statusesResult); + const openRule = rules.list.find( + (rule) => rule.issueMatchMode === "any" && rule.issueState === "open" + ); + const closedRule = rules.list.find( + (rule) => rule.issueMatchMode === "all" && rule.issueState === "closed" + ); + if (rules.isLoading) { + return ( + +

Loading rules…

+
+ ); + } + if (rules.loadFailed) { + return ( + +

Rules could not be loaded.

+
+ ); + } + return ( + +
+

Issue status rules

+

+ Update a post's Feeblo status as its linked GitHub issues change. +

+
+
+ + +
+
+ ); +} + +function GitHubSyncRuleSlot({ + args, + description, + issueMatchMode, + issueState, + onChanged, + rule, + statuses, + statusesLoading, +}: { + readonly args: { + readonly organizationId: string; + readonly connectionId: string; + }; + readonly description: string; + readonly issueMatchMode: GitHubSyncRule["issueMatchMode"]; + readonly issueState: GitHubSyncRule["issueState"]; + readonly onChanged: () => void; + readonly rule: GitHubSyncRule | undefined; + readonly statuses: readonly GitHubPostStatus[]; + readonly statusesLoading: boolean; +}) { + // The slot's rule is created on first change; remember its id so follow-up + // saves update it before the refreshed rule list arrives. + const [createdRuleId, setCreatedRuleId] = useState(null); + // Reuse the same in-flight (or just-resolved) create so concurrent first + // saves cannot issue duplicate createGitHubSyncRule calls. + const createInFlightRef = useRef | null>(null); + const [saving, setSaving] = useState(false); + const [draft, setDraft] = useState<{ + readonly postStatusId: string; + readonly upvoterNotificationPolicy: GitHubSyncRule["upvoterNotificationPolicy"]; + readonly enabled: boolean; + }>({ + postStatusId: rule?.postStatusId ?? statuses[0]?.id ?? "", + upvoterNotificationPolicy: + rule?.upvoterNotificationPolicy ?? "notify_upvoters", + enabled: rule?.enabled ?? false, + }); + // Re-derive the draft from the server rule once refreshes land. + useEffect(() => { + setDraft({ + postStatusId: rule?.postStatusId ?? statuses[0]?.id ?? "", + upvoterNotificationPolicy: + rule?.upvoterNotificationPolicy ?? "notify_upvoters", + enabled: rule?.enabled ?? false, + }); + }, [ + rule?.enabled, + rule?.postStatusId, + rule?.upvoterNotificationPolicy, + statuses[0]?.id, + ]); + const ruleId = rule?.id ?? createdRuleId; + const save = (next: { + readonly postStatusId: string; + readonly upvoterNotificationPolicy: GitHubSyncRule["upvoterNotificationPolicy"]; + readonly enabled: boolean; + }) => { + setDraft(next); + startTransition(async () => { + setSaving(true); + try { + if (ruleId === null) { + const reused = createInFlightRef.current !== null; + const createPromise = + createInFlightRef.current ?? + createGitHubSyncRule({ + organizationId: args.organizationId, + connectionId: args.connectionId, + issueMatchMode, + issueState, + postStatusId: next.postStatusId, + upvoterNotificationPolicy: next.upvoterNotificationPolicy, + enabled: next.enabled, + }); + createInFlightRef.current = createPromise; + let created: GitHubSyncRule; + try { + created = await createPromise; + } catch (error) { + if (!reused) { + createInFlightRef.current = null; + } + throw error; + } + setCreatedRuleId(created.id); + if (reused) { + await updateGitHubSyncRule({ + organizationId: args.organizationId, + connectionId: args.connectionId, + id: created.id, + postStatusId: next.postStatusId, + upvoterNotificationPolicy: next.upvoterNotificationPolicy, + enabled: next.enabled, + }); + } + } else { + await updateGitHubSyncRule({ + organizationId: args.organizationId, + connectionId: args.connectionId, + id: ruleId, + postStatusId: next.postStatusId, + upvoterNotificationPolicy: next.upvoterNotificationPolicy, + enabled: next.enabled, + }); + } + onChanged(); + } catch { + onChanged(); + toastManager.add({ + title: + ruleId === null + ? "Could not create GitHub synchronization rule" + : "Could not update GitHub synchronization rule", + type: "error", + }); + } finally { + setSaving(false); + } + }); + }; + const statusesReady = !statusesLoading && statuses.length > 0; + const remove = async () => { + if (ruleId === null) { + return; + } + setSaving(true); + try { + await deleteGitHubSyncRule({ + organizationId: args.organizationId, + connectionId: args.connectionId, + id: ruleId, + }); + setCreatedRuleId(null); + createInFlightRef.current = null; + onChanged(); + toastManager.add({ + title: "GitHub synchronization rule removed", + type: "success", + }); + } catch { + onChanged(); + toastManager.add({ + title: "Could not remove GitHub synchronization rule", + type: "error", + }); + } finally { + setSaving(false); + } + }; + return ( +
+
+ When + {description} +
+ { + Option.match( + Schema.decodeUnknownOption(PostStatusId.schema)( + String(postStatusId) + ), + { + onNone: () => + toastManager.add({ + title: "The selected Feeblo status is no longer available", + type: "error", + }), + onSome: (decodedPostStatusId) => + save({ ...draft, postStatusId: decodedPostStatusId }), + } + ); + }} + options={statuses.map( + (status) => [status.id, status.type.replaceAll("_", " ")] as const + )} + value={draft.postStatusId} + /> + + save({ + ...draft, + // SAFETY: the value originates from the hardcoded options below, so it is one of the allowed literals. + upvoterNotificationPolicy: + upvoterNotificationPolicy as GitHubSyncRule["upvoterNotificationPolicy"], + }) + } + options={[ + ["notify_upvoters", "Notify upvoters"], + ["do_not_notify_upvoters", "Don't notify"], + ]} + value={draft.upvoterNotificationPolicy} + /> +
+ save({ ...draft, enabled })} + /> + +
+
+ ); +} + +function RuleSelect({ + label, + value, + options, + disabled, + onValueChange, +}: { + readonly label: string; + readonly value: string; + readonly options: readonly (readonly [string, string])[]; + readonly disabled: boolean; + readonly onValueChange: (value: string) => void; +}) { + return ( +
+ {label} + +
+ ); +} diff --git a/apps/web/src/dashboard/features/github/components/post-github-actions.tsx b/apps/web/src/dashboard/features/github/components/post-github-actions.tsx new file mode 100644 index 00000000..0125ac90 --- /dev/null +++ b/apps/web/src/dashboard/features/github/components/post-github-actions.tsx @@ -0,0 +1,284 @@ +import { RegistryContext, useAtomValue } from "@effect/atom-react"; +import { Button } from "@feeblo/ui/button"; +import { + Dialog, + DialogClose, + DialogDescription, + DialogFooter, + DialogHeader, + DialogPanel, + DialogPopup, + DialogTitle, +} from "@feeblo/ui/dialog"; +import { Input } from "@feeblo/ui/input"; +import { MenuItem } from "@feeblo/ui/menu"; +import { + Select, + SelectItem, + SelectPopup, + SelectTrigger, + SelectValue, +} from "@feeblo/ui/select"; +import { toastManager } from "@feeblo/ui/toast"; +import { Link01Icon, PlusSignIcon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import * as Option from "effect/Option"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { useMemo, useState } from "react"; +import { usePostExternalResourceRefresh } from "~/features/integrations/components/post-external-resources"; +import { + gitHubAtomRegistry, + gitHubConnectionsAtom, + gitHubRepositoriesAtom, +} from "../atoms"; +import { + createGitHubPostIssue, + linkGitHubPostIssue, +} from "../lib/github-connections"; + +type GitHubPostAction = "create" | "link" | null; + +/** GitHub-owned issue actions contributed to the generic linked-resource menu. */ +export function GitHubPostResourceActions({ + organizationId, + postId, +}: { + readonly organizationId: string; + readonly postId: string; +}) { + return ( + + + + ); +} + +function GitHubPostResourceActionsContent({ + organizationId, + postId, +}: { + readonly organizationId: string; + readonly postId: string; +}) { + const [action, setAction] = useState(null); + const refreshPostExternalResources = usePostExternalResourceRefresh(); + return ( + <> + setAction("create")}> + + Create a new GitHub issue + + setAction("link")}> + + Link an existing GitHub issue + + {action ? ( + { + if (!open) { + setAction(null); + } + }} + organizationId={organizationId} + postId={postId} + /> + ) : null} + + ); +} + +function GitHubPostIssueDialog({ + action, + organizationId, + postId, + onChanged, + onOpenChange, +}: { + readonly action: Exclude; + readonly organizationId: string; + readonly postId: string; + readonly onChanged: () => void; + readonly onOpenChange: (open: boolean) => void; +}) { + const connectionsResult = useAtomValue(gitHubConnectionsAtom(organizationId)); + const connections = AsyncResult.match(connectionsResult, { + onInitial: () => [], + onFailure: ({ previousSuccess }) => + Option.getOrNull(previousSuccess)?.value ?? [], + onSuccess: ({ value }) => value, + }); + const [connectionId, setConnectionId] = useState(""); + const selectedConnectionId = connectionId || connections[0]?.id || ""; + const repositoriesResult = useAtomValue( + gitHubRepositoriesAtom({ + organizationId, + connectionId: selectedConnectionId, + }) + ); + const repositories = AsyncResult.match(repositoriesResult, { + onInitial: () => [], + onFailure: ({ previousSuccess }) => + Option.getOrNull(previousSuccess)?.value ?? [], + onSuccess: ({ value }) => value, + }); + const [repositoryFullName, setRepositoryFullName] = useState(""); + const [issueNumber, setIssueNumber] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [idempotencyKey] = useState(() => crypto.randomUUID()); + const submit = async () => { + const [repositoryOwner, repositoryName] = repositoryFullName.split("/"); + const parsedIssueNumber = Number(issueNumber); + if ( + !( + selectedConnectionId && + repositoryOwner && + repositoryName && + (action === "create" || + (Number.isInteger(parsedIssueNumber) && parsedIssueNumber > 0)) + ) + ) { + toastManager.add({ + title: "Choose a repository and enter a valid issue number", + type: "error", + }); + return; + } + setSubmitting(true); + try { + const input = { + organizationId, + postId, + connectionId: selectedConnectionId, + repositoryOwner, + repositoryName, + idempotencyKey, + }; + if (action === "create") { + await createGitHubPostIssue(input); + } else { + await linkGitHubPostIssue({ ...input, issueNumber: parsedIssueNumber }); + } + onChanged(); + onOpenChange(false); + toastManager.add({ + title: + action === "create" + ? "GitHub issue created by Feeblo bot" + : "GitHub issue linked and Feeblo bot comment added", + type: "success", + }); + } catch { + toastManager.add({ + title: + action === "create" + ? "Could not create GitHub issue" + : "Could not link GitHub issue or add the Feeblo bot comment", + type: "error", + }); + } finally { + setSubmitting(false); + } + }; + const repositoryOptions = useMemo( + () => repositories.map((repository) => repository.fullName), + [repositories] + ); + let submitLabel = "Link issue"; + if (submitting) { + submitLabel = "Saving…"; + } else if (action === "create") { + submitLabel = "Create issue"; + } + return ( + + + + + {action === "create" + ? "Create a GitHub issue" + : "Link a GitHub issue"} + + + {action === "create" + ? "Create an issue from this feedback post. The Feeblo bot will add a link back to this discussion." + : "Link this feedback post to an existing GitHub issue. The Feeblo bot will add a link back to this discussion as a comment."} + + + +
+ GitHub App installation + +
+
+ Repository + +
+ {action === "link" ? ( +
+ Issue number + setIssueNumber(event.target.value)} + placeholder="42" + type="number" + value={issueNumber} + /> +
+ ) : null} +
+ + }> + Cancel + + + +
+
+ ); +} diff --git a/apps/web/src/dashboard/features/github/lib/github-connections.ts b/apps/web/src/dashboard/features/github/lib/github-connections.ts new file mode 100644 index 00000000..7794e145 --- /dev/null +++ b/apps/web/src/dashboard/features/github/lib/github-connections.ts @@ -0,0 +1,121 @@ +import { fetchRpc } from "~/lib/runtime"; + +/** Reads whether the GitHub App is configured for this deployment. */ +export const loadGitHubIntegrationStatus = () => + fetchRpc((rpc) => rpc.GitHubIntegrationStatus()).then( + (result) => result.configured + ); + +/** Lists the safe GitHub connections belonging to an organization. */ +export const loadGitHubConnections = (organizationId: string) => + fetchRpc((rpc) => rpc.GitHubConnectionList({ organizationId })).then( + (result) => [...result] + ); + +/** Starts the organization-scoped GitHub App installation flow. */ +export const startGitHubConnect = (organizationId: string) => + fetchRpc((rpc) => rpc.GitHubConnectStart({ organizationId })); + +/** Removes a GitHub App connection from Feeblo. */ +export const disconnectGitHubConnection = (input: { + readonly organizationId: string; + readonly connectionId: string; +}) => fetchRpc((rpc) => rpc.GitHubConnectionDisconnect(input)); + +/** Lists repositories the connected GitHub account can publish issues to. */ +export const loadGitHubRepositories = (input: { + readonly organizationId: string; + readonly connectionId: string; +}) => + fetchRpc((rpc) => rpc.GitHubRepositoryList(input)).then((result) => [ + ...result, + ]); + +/** Reads automatic GitHub issue publishing settings for one connection. */ +export const loadGitHubPublishSettings = (input: { + readonly organizationId: string; + readonly connectionId: string; +}) => fetchRpc((rpc) => rpc.GitHubSettingsGet(input)); + +/** Updates the safe, non-secret automatic GitHub issue publishing settings. */ +export const updateGitHubPublishSettings = (input: { + readonly organizationId: string; + readonly connectionId: string; + readonly enabled: boolean; + readonly boardScope: "any_board" | "specific_board"; + readonly boardId: string | null; + readonly repositoryOwner: string | null; + readonly repositoryName: string | null; +}) => fetchRpc((rpc) => rpc.GitHubSettingsUpdate(input)); + +/** Lists issue-state-to-Feeblo-status synchronization rules for one connection. */ +export const loadGitHubSyncRules = (input: { + readonly organizationId: string; + readonly connectionId: string; +}) => + fetchRpc((rpc) => rpc.GitHubRuleList(input)).then((result) => [...result]); + +/** Creates a GitHub issue synchronization rule. */ +export const createGitHubSyncRule = (input: { + readonly organizationId: string; + readonly connectionId: string; + readonly issueMatchMode: "all" | "any"; + readonly issueState: "open" | "closed"; + readonly postStatusId: string; + readonly upvoterNotificationPolicy: + | "notify_upvoters" + | "do_not_notify_upvoters"; + readonly enabled: boolean; +}) => fetchRpc((rpc) => rpc.GitHubRuleCreate(input)); + +/** Updates the mutable fields of a hard-wired GitHub synchronization rule. */ +export const updateGitHubSyncRule = (input: { + readonly organizationId: string; + readonly connectionId: string; + readonly id: string; + readonly postStatusId: string; + readonly upvoterNotificationPolicy: + | "notify_upvoters" + | "do_not_notify_upvoters"; + readonly enabled: boolean; +}) => fetchRpc((rpc) => rpc.GitHubRuleUpdate(input)); + +/** Removes a GitHub issue synchronization rule. */ +export const deleteGitHubSyncRule = (input: { + readonly organizationId: string; + readonly connectionId: string; + readonly id: string; +}) => fetchRpc((rpc) => rpc.GitHubRuleDelete(input)); + +/** Creates and links a new GitHub issue for a Feeblo post. */ +export const createGitHubPostIssue = (input: { + readonly organizationId: string; + readonly postId: string; + readonly connectionId: string; + readonly repositoryOwner: string; + readonly repositoryName: string; + readonly idempotencyKey: string; +}) => fetchRpc((rpc) => rpc.GitHubPostIssueCreate(input)); + +/** Links an existing GitHub issue to a Feeblo post. */ +export const linkGitHubPostIssue = (input: { + readonly organizationId: string; + readonly postId: string; + readonly connectionId: string; + readonly repositoryOwner: string; + readonly repositoryName: string; + readonly issueNumber: number; + readonly idempotencyKey: string; +}) => fetchRpc((rpc) => rpc.GitHubPostIssueLink(input)); + +/** Lists boards for the automatic publishing scope selector. */ +export const loadGitHubBoards = (organizationId: string) => + fetchRpc((rpc) => rpc.BoardList({ organizationId })).then((result) => [ + ...result, + ]); + +/** Lists Feeblo statuses that GitHub state synchronization rules can set. */ +export const loadGitHubPostStatuses = (organizationId: string) => + fetchRpc((rpc) => rpc.PostStatusList({ organizationId })).then((result) => [ + ...result, + ]); diff --git a/apps/web/src/dashboard/features/integrations/atoms.ts b/apps/web/src/dashboard/features/integrations/atoms.ts new file mode 100644 index 00000000..af472906 --- /dev/null +++ b/apps/web/src/dashboard/features/integrations/atoms.ts @@ -0,0 +1,29 @@ +import * as Effect from "effect/Effect"; +import * as Atom from "effect/unstable/reactivity/Atom"; +import { loadPostExternalResourceLinks } from "./lib/post-external-resources"; + +/** Safe, provider-neutral external resource link returned for a feedback post. */ +export type PostExternalResourceLink = Awaited< + ReturnType +>[number]; + +/** Identifies one post whose external resource links should be cached. */ +export type PostExternalResourceLinkArgs = { + readonly organizationId: string; + readonly postId: string; +}; + +/** Cached provider-neutral external resource links for one feedback post. */ +export const postExternalResourceLinksAtom = Atom.family( + (args: PostExternalResourceLinkArgs) => + Atom.make( + Effect.tryPromise(() => loadPostExternalResourceLinks(args)) + ).pipe( + Atom.swr({ + staleTime: "15 seconds", + revalidateOnFocus: "always", + focusSignal: Atom.windowFocusSignal, + }), + Atom.setIdleTTL("5 minutes") + ) +); diff --git a/apps/web/src/dashboard/features/integrations/components/post-external-resources.tsx b/apps/web/src/dashboard/features/integrations/components/post-external-resources.tsx new file mode 100644 index 00000000..7e9119b3 --- /dev/null +++ b/apps/web/src/dashboard/features/integrations/components/post-external-resources.tsx @@ -0,0 +1,190 @@ +import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; +import { Button } from "@feeblo/ui/button"; +import { Menu, MenuPopup, MenuTrigger } from "@feeblo/ui/menu"; +import { LinkSquare02Icon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/react"; +import * as Option from "effect/Option"; +import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; +import { createContext, type ReactNode, useContext } from "react"; +import { + type PostExternalResourceLink, + postExternalResourceLinksAtom, +} from "../atoms"; + +type PostExternalResourceActionsContextValue = { + readonly refreshPostExternalResources: () => void; +}; + +const PostExternalResourceActionsContext = + createContext(null); + +/** Refreshes the provider-neutral external resource list after a provider mutation. */ +export function usePostExternalResourceRefresh() { + const context = useContext(PostExternalResourceActionsContext); + if (context === null) { + throw new Error( + "Post external resource refresh must be used within PostExternalResources." + ); + } + return context.refreshPostExternalResources; +} + +/** Displays every safe external resource linked to a post and hosts optional provider actions. */ +export function PostExternalResources({ + actions, + organizationId, + postId, +}: { + /** Provider-owned menu items and dialogs; providers may contribute none. */ + readonly actions?: ReactNode; + readonly organizationId: string; + readonly postId: string; +}) { + const resourcesResult = useAtomValue( + postExternalResourceLinksAtom({ organizationId, postId }) + ); + const refreshPostExternalResources = useAtomRefresh( + postExternalResourceLinksAtom({ organizationId, postId }) + ); + const resources = AsyncResult.match(resourcesResult, { + onInitial: () => null, + onFailure: ({ previousSuccess }) => + Option.getOrNull(previousSuccess)?.value ?? [], + onSuccess: ({ value }) => value, + }); + return ( + +
+
+

+ Linked resources +

+ {actions === undefined ? null : ( + + + } + > + + + {actions} + + )} +
+
+ +
+
+
+ ); +} + +function PostExternalResourceList({ + resources, +}: { + readonly resources: readonly PostExternalResourceLink[] | null; +}) { + if (resources === null) { + return ( +

Loading linked resources…

+ ); + } + if (resources.length === 0) { + return ( +

+ No external resources linked yet. +

+ ); + } + const resourceGroups = Map.groupBy( + resources, + (resource) => resource.provider + ); + return ( +
+ {[...resourceGroups.values()].map((providerResources) => ( + + ))} +
+ ); +} + +function PostExternalResourceProviderGroup({ + resources, +}: { + readonly resources: readonly PostExternalResourceLink[]; +}) { + const providerDisplayName = resources[0]?.providerDisplayName ?? "External"; + return ( +
+

+ + + {providerDisplayName} + +

+
+ {resources.map((resource) => ( + + ))} +
+
+ ); +} + +function PostExternalResourceCard({ + resource, +}: { + readonly resource: PostExternalResourceLink; +}) { + const resourceLabel = + resource.title ?? resource.displayKey ?? resource.resourceType; + const resourceDetails = [resource.resourceType, resource.displayKey] + .filter((value): value is string => value !== null) + .join(" · "); + const content = ( + <> + + {resourceLabel} + {resourceDetails === "" ? null : ( + + {resourceDetails} + + )} + + {resource.stateKey === null ? null : ( + + {resource.stateKey} + + )} + + ); + return ( + + {content} + + ); +} diff --git a/apps/web/src/dashboard/features/integrations/lib/post-external-resources.ts b/apps/web/src/dashboard/features/integrations/lib/post-external-resources.ts new file mode 100644 index 00000000..756f7fa0 --- /dev/null +++ b/apps/web/src/dashboard/features/integrations/lib/post-external-resources.ts @@ -0,0 +1,10 @@ +import { fetchRpc } from "~/lib/runtime"; + +/** Lists safe external resources linked to one feedback post across providers. */ +export const loadPostExternalResourceLinks = (input: { + readonly organizationId: string; + readonly postId: string; +}) => + fetchRpc((rpc) => rpc.PostExternalResourceLinkList(input)).then((result) => [ + ...result, + ]); diff --git a/apps/web/src/dashboard/routeTree.gen.ts b/apps/web/src/dashboard/routeTree.gen.ts index ab403088..f594f299 100644 --- a/apps/web/src/dashboard/routeTree.gen.ts +++ b/apps/web/src/dashboard/routeTree.gen.ts @@ -52,6 +52,7 @@ import { Route as OrganizationIdDashboardLayoutBoardBoardSlugBacklogRouteImport import { Route as OrganizationIdDashboardLayoutChangelogEditChangelogSlugRouteImport } from "./routes/$organizationId/_dashboard-layout/changelog/edit/$changelogSlug" import { Route as OrganizationIdDashboardLayoutPostBoardSlugPostSlugRouteImport } from "./routes/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug" import { Route as OrganizationIdSettingsIntegrationsDiscordIndexRouteImport } from "./routes/$organizationId/settings/integrations/discord/index" +import { Route as OrganizationIdSettingsIntegrationsGithubIndexRouteImport } from "./routes/$organizationId/settings/integrations/github/index" import { Route as OrganizationIdSettingsIntegrationsSlackIndexRouteImport } from "./routes/$organizationId/settings/integrations/slack/index" const OrganizationIdRoute = OrganizationIdRouteImport.update({ @@ -303,6 +304,12 @@ const OrganizationIdSettingsIntegrationsDiscordIndexRoute = path: "/integrations/discord/", getParentRoute: () => OrganizationIdSettingsRoute, } as any) +const OrganizationIdSettingsIntegrationsGithubIndexRoute = + OrganizationIdSettingsIntegrationsGithubIndexRouteImport.update({ + id: "/integrations/github/", + path: "/integrations/github/", + getParentRoute: () => OrganizationIdSettingsRoute, + } as any) const OrganizationIdSettingsIntegrationsSlackIndexRoute = OrganizationIdSettingsIntegrationsSlackIndexRouteImport.update({ id: "/integrations/slack/", @@ -353,6 +360,7 @@ export interface FileRoutesByFullPath { "/$organizationId/post/$boardSlug/$postSlug": typeof OrganizationIdDashboardLayoutPostBoardSlugPostSlugRoute "/$organizationId/board/$boardSlug/": typeof OrganizationIdDashboardLayoutBoardBoardSlugIndexRoute "/$organizationId/settings/integrations/discord/": typeof OrganizationIdSettingsIntegrationsDiscordIndexRoute + "/$organizationId/settings/integrations/github/": typeof OrganizationIdSettingsIntegrationsGithubIndexRoute "/$organizationId/settings/integrations/slack/": typeof OrganizationIdSettingsIntegrationsSlackIndexRoute } export interface FileRoutesByTo { @@ -396,6 +404,7 @@ export interface FileRoutesByTo { "/$organizationId/post/$boardSlug/$postSlug": typeof OrganizationIdDashboardLayoutPostBoardSlugPostSlugRoute "/$organizationId/board/$boardSlug": typeof OrganizationIdDashboardLayoutBoardBoardSlugIndexRoute "/$organizationId/settings/integrations/discord": typeof OrganizationIdSettingsIntegrationsDiscordIndexRoute + "/$organizationId/settings/integrations/github": typeof OrganizationIdSettingsIntegrationsGithubIndexRoute "/$organizationId/settings/integrations/slack": typeof OrganizationIdSettingsIntegrationsSlackIndexRoute } export interface FileRoutesById { @@ -443,6 +452,7 @@ export interface FileRoutesById { "/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug": typeof OrganizationIdDashboardLayoutPostBoardSlugPostSlugRoute "/$organizationId/_dashboard-layout/board/$boardSlug/": typeof OrganizationIdDashboardLayoutBoardBoardSlugIndexRoute "/$organizationId/settings/integrations/discord/": typeof OrganizationIdSettingsIntegrationsDiscordIndexRoute + "/$organizationId/settings/integrations/github/": typeof OrganizationIdSettingsIntegrationsGithubIndexRoute "/$organizationId/settings/integrations/slack/": typeof OrganizationIdSettingsIntegrationsSlackIndexRoute } export interface FileRouteTypes { @@ -490,6 +500,7 @@ export interface FileRouteTypes { | "/$organizationId/post/$boardSlug/$postSlug" | "/$organizationId/board/$boardSlug/" | "/$organizationId/settings/integrations/discord/" + | "/$organizationId/settings/integrations/github/" | "/$organizationId/settings/integrations/slack/" fileRoutesByTo: FileRoutesByTo to: @@ -533,6 +544,7 @@ export interface FileRouteTypes { | "/$organizationId/post/$boardSlug/$postSlug" | "/$organizationId/board/$boardSlug" | "/$organizationId/settings/integrations/discord" + | "/$organizationId/settings/integrations/github" | "/$organizationId/settings/integrations/slack" id: | "__root__" @@ -579,6 +591,7 @@ export interface FileRouteTypes { | "/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug" | "/$organizationId/_dashboard-layout/board/$boardSlug/" | "/$organizationId/settings/integrations/discord/" + | "/$organizationId/settings/integrations/github/" | "/$organizationId/settings/integrations/slack/" fileRoutesById: FileRoutesById } @@ -895,6 +908,13 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof OrganizationIdSettingsIntegrationsDiscordIndexRouteImport parentRoute: typeof OrganizationIdSettingsRoute } + "/$organizationId/settings/integrations/github/": { + id: "/$organizationId/settings/integrations/github/" + path: "/integrations/github" + fullPath: "/$organizationId/settings/integrations/github/" + preLoaderRoute: typeof OrganizationIdSettingsIntegrationsGithubIndexRouteImport + parentRoute: typeof OrganizationIdSettingsRoute + } "/$organizationId/settings/integrations/slack/": { id: "/$organizationId/settings/integrations/slack/" path: "/integrations/slack" @@ -984,6 +1004,7 @@ interface OrganizationIdSettingsRouteChildren { OrganizationIdSettingsIntegrationsIndexRoute: typeof OrganizationIdSettingsIntegrationsIndexRoute OrganizationIdSettingsWebhooksIndexRoute: typeof OrganizationIdSettingsWebhooksIndexRoute OrganizationIdSettingsIntegrationsDiscordIndexRoute: typeof OrganizationIdSettingsIntegrationsDiscordIndexRoute + OrganizationIdSettingsIntegrationsGithubIndexRoute: typeof OrganizationIdSettingsIntegrationsGithubIndexRoute OrganizationIdSettingsIntegrationsSlackIndexRoute: typeof OrganizationIdSettingsIntegrationsSlackIndexRoute } @@ -1017,6 +1038,8 @@ const OrganizationIdSettingsRouteChildren: OrganizationIdSettingsRouteChildren = OrganizationIdSettingsWebhooksIndexRoute, OrganizationIdSettingsIntegrationsDiscordIndexRoute: OrganizationIdSettingsIntegrationsDiscordIndexRoute, + OrganizationIdSettingsIntegrationsGithubIndexRoute: + OrganizationIdSettingsIntegrationsGithubIndexRoute, OrganizationIdSettingsIntegrationsSlackIndexRoute: OrganizationIdSettingsIntegrationsSlackIndexRoute, } diff --git a/apps/web/src/dashboard/routes/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug.tsx b/apps/web/src/dashboard/routes/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug.tsx index e0b23141..36d1fe01 100644 --- a/apps/web/src/dashboard/routes/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug.tsx +++ b/apps/web/src/dashboard/routes/$organizationId/_dashboard-layout/post/$boardSlug/$postSlug.tsx @@ -11,6 +11,7 @@ import { import { Separator } from "@feeblo/ui/separator"; import { Skeleton } from "@feeblo/ui/skeleton"; import { Tabs, TabsList, TabsPanel, TabsTab } from "@feeblo/ui/tabs"; +import { hasPermission, usePolicy } from "@feeblo/web-shared/use-policy"; import { Activity01Icon, Calendar03Icon, @@ -22,6 +23,8 @@ import { HugeiconsIcon } from "@hugeicons/react"; import { and, eq, useLiveQuery } from "@tanstack/react-db"; import { createFileRoute, Link } from "@tanstack/react-router"; import { formatPostDate } from "~/features/board/components/board-surface/utils"; +import { GitHubPostResourceActions } from "~/features/github/components/post-github-actions"; +import { PostExternalResources } from "~/features/integrations/components/post-external-resources"; import { PostActivityList } from "~/features/post/components/post-activity-list"; import { PostBoardField } from "~/features/post/components/post-board-field"; import { PostEtaField } from "~/features/post/components/post-eta-field"; @@ -63,6 +66,12 @@ export const Route = createFileRoute( function RouteComponent() { const { organizationId, boardSlug, postSlug } = Route.useParams(); const { boardCollection, postCollection } = useDashboardCollections(); + // The linked-resources panel and its GitHub actions are read/written through + // RPCs that require integrations.manage, so gate them the same way the + // settings route does instead of rendering actions that will 403. + const githubResourcesPolicy = usePolicy( + hasPermission(organizationId, "integrations.manage") + ); const { data: postRow, isLoading: isPostLoading } = useLiveQuery( (q) => { @@ -181,6 +190,20 @@ function RouteComponent() {
+ {githubResourcesPolicy.isPending || + !githubResourcesPolicy.allowed ? null : ( + + } + organizationId={organizationId} + postId={post.id} + /> + )} + {/* Each field self-gates with the permission the backend enforces (PostPolicy.canUpdateProperties): status → posts.status, board → posts.move, ETA → posts.status. */} diff --git a/apps/web/src/dashboard/routes/$organizationId/settings/integrations/github/index.tsx b/apps/web/src/dashboard/routes/$organizationId/settings/integrations/github/index.tsx new file mode 100644 index 00000000..83d24158 --- /dev/null +++ b/apps/web/src/dashboard/routes/$organizationId/settings/integrations/github/index.tsx @@ -0,0 +1,36 @@ +import { hasPermission, usePolicy } from "@feeblo/web-shared/use-policy"; +import { createFileRoute } from "@tanstack/react-router"; +import { GitHubSettings } from "~/features/github/components/github-settings"; +import { SettingsAccessDenied } from "~/features/settings/components/settings-access-denied"; +import { SettingsLayout } from "~/features/settings/components/settings-layout"; +import { useOrganizationId } from "~/hooks/use-organization-id"; + +export const Route = createFileRoute( + "/$organizationId/settings/integrations/github/" +)({ component: GitHubIntegrationSettingsRoute }); + +function GitHubIntegrationSettingsRoute() { + const organizationId = useOrganizationId(); + const { allowed, isPending } = usePolicy( + hasPermission(organizationId, "integrations.manage") + ); + if (isPending) { + return null; + } + if (!allowed) { + return ; + } + return ( + + + GitHub + + Publish feedback to GitHub issues and synchronize linked issue status. + + + + + + + ); +} diff --git a/apps/web/src/dashboard/routes/$organizationId/settings/integrations/index.tsx b/apps/web/src/dashboard/routes/$organizationId/settings/integrations/index.tsx index ba2319a5..7b371b3e 100644 --- a/apps/web/src/dashboard/routes/$organizationId/settings/integrations/index.tsx +++ b/apps/web/src/dashboard/routes/$organizationId/settings/integrations/index.tsx @@ -3,7 +3,11 @@ import { Button } from "@feeblo/ui/button"; import { Card, CardPanel } from "@feeblo/ui/card"; import { toastManager } from "@feeblo/ui/toast"; import { hasPermission, usePolicy } from "@feeblo/web-shared/use-policy"; -import { Chat01Icon, ChatBotIcon } from "@hugeicons/core-free-icons"; +import { + Chat01Icon, + ChatBotIcon, + LinkSquare02Icon, +} from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; import { createFileRoute, useRouter } from "@tanstack/react-router"; import * as Option from "effect/Option"; @@ -19,6 +23,15 @@ import { type loadConnections as loadDiscordConnections, startDiscordConnect, } from "~/features/discord/lib/connections"; +import { + gitHubAtomRegistry, + gitHubConnectionsAtom, + gitHubIntegrationStatusAtom, +} from "~/features/github/atoms"; +import { + type loadGitHubConnections, + startGitHubConnect, +} from "~/features/github/lib/github-connections"; import { SettingsAccessDenied } from "~/features/settings/components/settings-access-denied"; import { SettingsLayout } from "~/features/settings/components/settings-layout"; import { @@ -38,6 +51,7 @@ export const Route = createFileRoute("/$organizationId/settings/integrations/")( z .object({ discord: z.enum(["connected", "error"]).optional(), + github: z.enum(["connected", "error"]).optional(), slack: z.enum(["connected", "error"]).optional(), message: z.string().min(1).optional(), }) @@ -54,9 +68,8 @@ function IntegrationsSettingsRoute() { const search = Route.useSearch(); const router = useRouter(); - // Surface the result of the Slack and Discord OAuth install flows the - // server redirected back with, then strip the query params so the notice - // shows only once. + // Surface the result of provider connection flows the server redirected back + // with, then strip the query params so the notice shows only once. useEffect(() => { if (search.slack !== undefined) { if (search.slack === "connected") { @@ -76,6 +89,19 @@ function IntegrationsSettingsRoute() { type: "error", }); } + } else if (search.github !== undefined) { + if (search.github === "connected") { + toastManager.add({ + title: "GitHub App installed", + description: "Feeblo can now create issues and comments as its bot.", + type: "success", + }); + } else { + toastManager.add({ + title: search.message ?? "Could not connect GitHub", + type: "error", + }); + } } else { return; } @@ -85,7 +111,14 @@ function IntegrationsSettingsRoute() { search: {}, replace: true, }); - }, [organizationId, router, search.discord, search.message, search.slack]); + }, [ + organizationId, + router, + search.discord, + search.github, + search.message, + search.slack, + ]); if (isPending) { return null; @@ -110,12 +143,129 @@ function IntegrationsSettingsRoute() { + + +
); } +function GitHubIntegrationCard({ + organizationId, +}: { + readonly organizationId: string; +}) { + const router = useRouter(); + const [connecting, setConnecting] = useState(false); + const connectionsResult = useAtomValue(gitHubConnectionsAtom(organizationId)); + const statusResult = useAtomValue(gitHubIntegrationStatusAtom); + const configured = AsyncResult.match(statusResult, { + onInitial: () => null as boolean | null, + onFailure: () => false, + onSuccess: ({ value }) => value, + }); + const { connections, isLoading, loadFailed } = AsyncResult.match( + connectionsResult, + { + onInitial: () => ({ + connections: [] as Awaited>, + isLoading: true, + loadFailed: false, + }), + onFailure: ({ previousSuccess }) => + Option.match(previousSuccess, { + onNone: () => ({ + connections: [], + isLoading: false, + loadFailed: true, + }), + onSome: ({ value }) => ({ + connections: value, + isLoading: false, + loadFailed: false, + }), + }), + onSuccess: ({ value }) => ({ + connections: value, + isLoading: false, + loadFailed: false, + }), + } + ); + const connected = connections.some( + (connection) => + connection.lifecycle === "active" || connection.lifecycle === "connecting" + ); + const connect = async () => { + setConnecting(true); + try { + const { authorizeUrl } = await startGitHubConnect(organizationId); + window.location.assign(authorizeUrl.toString()); + } catch { + setConnecting(false); + toastManager.add({ + title: "Could not start GitHub App installation", + type: "error", + }); + } + }; + if (configured === null) { + return ( + + +

Loading GitHub…

+
+
+ ); + } + if (!configured) { + return null; + } + return ( + + +
+
+
+ +
+
+
+

GitHub

+ {connectionStatusBadge({ connected, isLoading, loadFailed })} +
+

+ Choose repositories for the Feeblo bot to publish feedback as + GitHub issues and comments. +

+
+
+
+ {connected ? ( + + ) : ( + + )} +
+
+
+
+ ); +} + function connectionStatusBadge({ connected, isLoading, diff --git a/docs/adr/0001-transactional-integration-events-and-provider-adapters.md b/docs/adr/0001-transactional-integration-events-and-provider-adapters.md index 118f59fb..603dc220 100644 --- a/docs/adr/0001-transactional-integration-events-and-provider-adapters.md +++ b/docs/adr/0001-transactional-integration-events-and-provider-adapters.md @@ -12,4 +12,4 @@ The transaction ends before any provider request. This yields at-least-once deli ## Consequences -V1 has only signed outbound custom webhooks. Inbox events, bindings, and bidirectional synchronization remain future concepts and are not represented as implemented behavior here. +V1 initially shipped only signed outbound custom webhooks. Inbox events and post-to-external-resource bindings arrived with the GitHub provider: a signature-verified, delivery-deduplicated issue webhook feeds organization-owned sync rules, and provider-owned issues are recorded as external-resource links on posts. Bidirectional synchronization (field ownership and conflict handling) remains a future concept. diff --git a/docs/integrations.md b/docs/integrations.md index 51f0adf5..428b1f07 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -24,7 +24,16 @@ Application-wide credentials differ from Slack: the bot token and interaction pu Inbound requests are signature-verified (Ed25519, `X-Signature-Ed25519` over `X-Signature-Timestamp + body`, 5-minute freshness window) by the provider before any domain work. The server mounts `/discord/oauth/callback` and `/discord/interactions`. The dashboard settings page (`/$organizationId/settings/integrations/discord`, `integrations.manage`) handles server connect/disconnect and per-channel notification toggles. -Linear, HubSpot, inbox processing, bindings, bidirectional synchronization, and in-modal similar-request upvoting are future phases, not packages or capabilities supplied by V1. +## GitHub provider + +`integrations/github` implements the GitHub provider (`github`, GitHub App, `github_app` connection mode) — the first provider to supply inbound and binding behavior: + +- **`github.issue.create`** (outbound) — creating and linking GitHub issues from Feeblo posts; a created issue carries the post's title and description, and the bot comments a Feeblo backlink ("The issue is linked to our feedback platform. For feedback and updates, please visit [this link](…)") on both created and linked issues. Issues produced by a delivery are recorded as provider-neutral external-resource links on the post. +- **`github.issue.webhook`** (inbound) — the global App webhook (`/github/app/webhooks`) is signature-verified and deduplicated by GitHub delivery ID before any domain work; issue state changes map to Feeblo post statuses through organization-owned sync rules and may notify the post's upvoters. + +External resources are the V1 form of bindings: `integration_external_resource` / `post_external_resource_link` (and `packages/domain`'s provider-neutral `external-resource` service) hold one-to-many links from a Feeblo post to provider-owned resources. The server mounts `/github/app/installations/callback` and `/github/app/webhooks`; installation access tokens are never stored, and the App credentials (ID, client ID/secret, private key, webhook secret) live in configuration. The dashboard settings page (`/$organizationId/settings/integrations/github`, `integrations.manage`) handles App installation and issue-sync rules; the per-post create/link actions and linked-resource panel are gated on the same `integrations.manage` permission. Sync rules are hard-wired to two shapes per connection — (any, open) sets a status when any linked issue is open, (all, closed) sets one when every linked issue is closed — at most one rule per shape and each individually disableable. The two shapes can never match the same issue aggregate, so rule application is deterministic. + +Linear and HubSpot providers, bidirectional synchronization (field ownership, conflict handling, and reconciliation), and in-modal similar-request upvoting remain future phases. ## Operations diff --git a/integrations/core/package.json b/integrations/core/package.json index 89223982..70f0881b 100644 --- a/integrations/core/package.json +++ b/integrations/core/package.json @@ -23,7 +23,7 @@ "effect": "catalog:" }, "devDependencies": { - "@effect/vitest": "4.0.0-beta.94", + "@effect/vitest": "catalog:", "@feeblo/config": "workspace:*", "typescript": "catalog:", "vitest": "catalog:" diff --git a/integrations/core/src/credential-encryption.ts b/integrations/core/src/credential-encryption.ts index 77e8a3b2..f17fec67 100644 --- a/integrations/core/src/credential-encryption.ts +++ b/integrations/core/src/credential-encryption.ts @@ -4,7 +4,7 @@ import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; /** Failure encrypting or decrypting provider credentials at rest. */ -export class IntegrationCredentialEncryptionError extends Schema.TaggedErrorClass()( +export class IntegrationCredentialEncryptionError extends Schema.TaggedError()( "IntegrationCredentialEncryptionError", { operation: Schema.Literals(["encrypt", "decrypt"]), reason: Schema.String } ) {} diff --git a/integrations/core/src/integration-contracts.ts b/integrations/core/src/integration-contracts.ts index 890126c9..2a9f107a 100644 --- a/integrations/core/src/integration-contracts.ts +++ b/integrations/core/src/integration-contracts.ts @@ -6,6 +6,7 @@ import { IntegrationDeliveryRetryDecision as DbIntegrationDeliveryRetryDecision, IntegrationDeliveryState as DbIntegrationDeliveryState, IntegrationEventType as DbIntegrationEventType, + IntegrationExternalResourceType as DbIntegrationExternalResourceType, IntegrationProviderKey as DbIntegrationProviderKey, IntegrationRouteEventSelection as DbIntegrationRouteEventSelection, IntegrationSafeDisplayMetadata as DbIntegrationSafeDisplayMetadata, @@ -16,6 +17,7 @@ import { type TIntegrationConnectionMode, type TIntegrationDeliveryState, type TIntegrationEventType, + type TIntegrationExternalResourceType, type TIntegrationProviderKey, type TSubscribableIntegrationEventType, } from "@feeblo/db/validation-schema/integration"; @@ -44,6 +46,8 @@ export const IntegrationConnectionMode = DbIntegrationConnectionMode; export const IntegrationDeliveryRetryDecision = DbIntegrationDeliveryRetryDecision; export const IntegrationDeliveryState = DbIntegrationDeliveryState; +export const IntegrationExternalResourceType = + DbIntegrationExternalResourceType; export const IntegrationEventType = DbIntegrationEventType; export const IntegrationProviderKey = DbIntegrationProviderKey; export const IntegrationRouteEventSelection = DbIntegrationRouteEventSelection; @@ -131,6 +135,8 @@ export const IntegrationPostEventData = Schema.Struct({ }), post: Schema.Struct({ id: PostId.schema, + /** Post body (sanitized markdown) used as the provider issue body; absent for status-change events. */ + description: Schema.optionalKey(Schema.String), metadata: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)), status: Schema.Struct({ id: PostStatusId.schema, @@ -152,6 +158,7 @@ export type IntegrationConnectionLifecycleStatus = /** Durable delivery lifecycle; a lease is never an external request by itself. */ export type IntegrationDeliveryState = TIntegrationDeliveryState; +export type IntegrationExternalResourceType = TIntegrationExternalResourceType; /** Immutable route record fields needed for matching and delivery persistence. */ export const IntegrationRoute = Schema.Struct({ @@ -216,7 +223,7 @@ export interface IntegrationDeliveryAttempt extends Schema.Schema.Type {} /** Persistence failure while atomically recording an event and its matched deliveries. */ -export class IntegrationEventRecordingError extends Schema.TaggedErrorClass()( +export class IntegrationEventRecordingError extends Schema.TaggedError()( "IntegrationEventRecordingError", { message: Schema.String } ) {} @@ -248,7 +255,7 @@ export class IntegrationEventRecorder extends Context.Service< >()("@feeblo/IntegrationEventRecorder") {} /** Provider-side authentication failure requiring reconnection or remediation. */ -export class IntegrationProviderAuthenticationError extends Schema.TaggedErrorClass()( +export class IntegrationProviderAuthenticationError extends Schema.TaggedError()( "IntegrationProviderAuthenticationError", { httpStatus: Schema.optionalKey(Schema.Int), @@ -258,7 +265,7 @@ export class IntegrationProviderAuthenticationError extends Schema.TaggedErrorCl ) {} /** Provider-side rate limiting; retryAfterMs is validated before retry scheduling. */ -export class IntegrationProviderRateLimitedError extends Schema.TaggedErrorClass()( +export class IntegrationProviderRateLimitedError extends Schema.TaggedError()( "IntegrationProviderRateLimitedError", { message: Schema.String, @@ -271,7 +278,7 @@ export class IntegrationProviderRateLimitedError extends Schema.TaggedErrorClass ) {} /** Provider configuration rejected before an outbound request can be made. */ -export class IntegrationProviderInvalidConfigurationError extends Schema.TaggedErrorClass()( +export class IntegrationProviderInvalidConfigurationError extends Schema.TaggedError()( "IntegrationProviderInvalidConfigurationError", { httpStatus: Schema.optionalKey(Schema.Int), @@ -281,7 +288,7 @@ export class IntegrationProviderInvalidConfigurationError extends Schema.TaggedE ) {} /** Retryable provider or transport failure; secrets and raw response bodies stay private. */ -export class IntegrationProviderTemporaryFailure extends Schema.TaggedErrorClass()( +export class IntegrationProviderTemporaryFailure extends Schema.TaggedError()( "IntegrationProviderTemporaryFailure", { httpStatus: Schema.optionalKey(Schema.Int), @@ -291,7 +298,7 @@ export class IntegrationProviderTemporaryFailure extends Schema.TaggedErrorClass ) {} /** Terminal provider rejection; retrying the same request cannot fix it. */ -export class IntegrationProviderPermanentRejection extends Schema.TaggedErrorClass()( +export class IntegrationProviderPermanentRejection extends Schema.TaggedError()( "IntegrationProviderPermanentRejection", { httpStatus: Schema.optionalKey(Schema.Int), @@ -316,8 +323,28 @@ export interface IntegrationProviderDeliveryInput { readonly route: IntegrationRoute; } +/** + * A provider-normalized external resource produced by one successful delivery. + * Provider credentials and addressing details belong only in safe metadata. + */ +export const IntegrationExternalResourceDraft = Schema.Struct({ + displayKey: Schema.optionalKey(Schema.NonEmptyString), + /** Feeblo post that owns this external resource link. */ + postId: PostId.schema, + remoteId: Schema.NonEmptyString, + stateKey: Schema.optionalKey(Schema.NonEmptyString), + remoteUrl: Schema.URLFromString, + resourceType: IntegrationExternalResourceType, + safeMetadata: IntegrationSafeDisplayMetadata, + title: Schema.optionalKey(Schema.String), +}); +export interface IntegrationExternalResourceDraft + extends Schema.Schema.Type {} + /** Provider handler reports only safe outcome metadata to the delivery kernel. */ export interface IntegrationProviderDeliveryResult { + /** Resource links to persist with delivery success, guarded by the delivery lease. */ + readonly externalResourceDrafts?: readonly IntegrationExternalResourceDraft[]; readonly httpStatus?: number; } @@ -345,7 +372,7 @@ export interface IntegrationInboundResponse { } /** Terminal rejection of an inbound request after verification failed. */ -export class IntegrationInboundRejection extends Schema.TaggedErrorClass()( +export class IntegrationInboundRejection extends Schema.TaggedError()( "IntegrationInboundRejection", { message: Schema.String, provider: IntegrationProviderKey } ) {} diff --git a/integrations/core/src/integration-delivery-postgres-repository.ts b/integrations/core/src/integration-delivery-postgres-repository.ts index 6694fe95..bcd0dff7 100644 --- a/integrations/core/src/integration-delivery-postgres-repository.ts +++ b/integrations/core/src/integration-delivery-postgres-repository.ts @@ -1,5 +1,8 @@ import { currentDb, type Database, schema } from "@feeblo/db"; -import type { TIntegrationCapabilityKey } from "@feeblo/db/validation-schema/integration"; +import type { + TIntegrationCapabilityKey, + TIntegrationProviderKey, +} from "@feeblo/db/validation-schema/integration"; import { asLegid, IntegrationConnectionId, @@ -9,7 +12,7 @@ import { IntegrationRouteId, WorkspaceId, } from "@feeblo/id"; -import { and, eq, gt, inArray, isNull, lte, sql } from "drizzle-orm"; +import { and, eq, gt, inArray, isNull, lte, or, sql } from "drizzle-orm"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Random from "effect/Random"; @@ -20,6 +23,7 @@ import { IntegrationConnection, IntegrationDelivery, IntegrationEventEnvelopeV1, + type IntegrationExternalResourceDraft, IntegrationRoute, } from "./integration-contracts"; import { @@ -70,11 +74,16 @@ const mapPersistenceError = ( /** * PostgreSQL persistence boundary for lease ownership; it never performs - * provider I/O. Deliveries are claimed only for the supplied outbound - * capability keys, which the startup-validated provider registry owns. + * provider I/O. Deliveries are claimed only for capabilities owned by the + * matching provider, which the startup-validated provider registry supplies. */ export const makeIntegrationDeliveryWorkerRepository = ( - claimableCapabilityKeys: readonly string[] + claimableCapabilityKeysByProvider: ReadonlyMap, + recordExternalResourceDrafts?: (input: { + readonly connection: IntegrationConnection; + readonly drafts: readonly IntegrationExternalResourceDraft[]; + readonly event: IntegrationEventEnvelopeV1; + }) => Effect.Effect ): Effect.Effect< IntegrationDeliveryWorkerRepository, never, @@ -189,6 +198,24 @@ export const makeIntegrationDeliveryWorkerRepository = ( "claim_due_deliveries", Effect.gen(function* () { const now = yield* DateTime.nowAsDate; + const providerCapabilityConditions = Array.from( + claimableCapabilityKeysByProvider.entries() + ).map(([provider, keys]) => + and( + eq( + schema.integrationConnectionTable.provider, + provider as TIntegrationProviderKey + ), + inArray( + schema.integrationRouteTable.capabilityKey, + // SAFETY: the claimable keys come from the + // startup-validated provider registry, which constrains + // them to the canonical capability vocabulary; unknown or + // cross-provider keys simply never match a stored route. + keys as readonly TIntegrationCapabilityKey[] + ) + ) + ); const [backlog] = yield* db .select({ count: sql`count(*)` }) .from(schema.integrationDeliveryTable) @@ -226,14 +253,9 @@ export const makeIntegrationDeliveryWorkerRepository = ( lte(schema.integrationDeliveryTable.nextAttemptAt, now), eq(schema.integrationConnectionTable.lifecycle, "active"), eq(schema.integrationRouteTable.enabled, true), - inArray( - schema.integrationRouteTable.capabilityKey, - // SAFETY: the claimable keys come from the - // startup-validated provider registry, which constrains - // them to the canonical capability vocabulary; unknown - // keys simply never match a stored route capability. - claimableCapabilityKeys as readonly TIntegrationCapabilityKey[] - ) + providerCapabilityConditions.length === 0 + ? sql`false` + : or(...providerCapabilityConditions) ) ) .orderBy(schema.integrationDeliveryTable.nextAttemptAt) @@ -349,7 +371,7 @@ export const makeIntegrationDeliveryWorkerRepository = ( ); const persistDeliveryResult: IntegrationDeliveryWorkerRepository["persistDeliveryResult"] = - ({ claimed, errorTag, httpStatus, outcome }) => + ({ claimed, errorTag, externalResourceDrafts, httpStatus, outcome }) => mapPersistenceError( "persist_delivery_result", db.transaction(() => @@ -424,6 +446,21 @@ export const makeIntegrationDeliveryWorkerRepository = ( ); } if (decision._tag === "Succeeded") { + if ( + externalResourceDrafts !== undefined && + externalResourceDrafts.length > 0 + ) { + if (recordExternalResourceDrafts === undefined) { + return yield* persistenceError( + "record_external_resource_drafts" + ); + } + yield* recordExternalResourceDrafts({ + connection: claimed.input.connection, + drafts: externalResourceDrafts, + event: claimed.input.event, + }); + } yield* db .update(schema.integrationDeliveryTable) .set({ diff --git a/integrations/core/src/integration-delivery-worker.ts b/integrations/core/src/integration-delivery-worker.ts index 2d873a3d..dcdc6879 100644 --- a/integrations/core/src/integration-delivery-worker.ts +++ b/integrations/core/src/integration-delivery-worker.ts @@ -9,7 +9,10 @@ import { type IntegrationDeliveryOutcome, } from "./delivery-policy"; import { integrationDeliveryWorkerDefaults } from "./delivery-worker-defaults"; -import type { IntegrationProviderDeliveryInput } from "./integration-contracts"; +import type { + IntegrationExternalResourceDraft, + IntegrationProviderDeliveryInput, +} from "./integration-contracts"; import { recordIntegrationClaimedBacklog, recordIntegrationDeliveryOutcome, @@ -23,7 +26,7 @@ export interface ClaimedIntegrationDelivery { } /** Safe persistence failure surfaced by a worker poll without leaking payloads or credentials. */ -export class IntegrationDeliveryWorkerPersistenceError extends Schema.TaggedErrorClass()( +export class IntegrationDeliveryWorkerPersistenceError extends Schema.TaggedError()( "IntegrationDeliveryWorkerPersistenceError", { operation: Schema.String } ) {} @@ -49,6 +52,8 @@ export interface IntegrationDeliveryWorkerRepository { readonly persistDeliveryResult: (input: { readonly claimed: ClaimedIntegrationDelivery; readonly errorTag?: string; + /** Provider-normalized resources persisted only with a successful delivery. */ + readonly externalResourceDrafts?: readonly IntegrationExternalResourceDraft[]; readonly httpStatus?: number; readonly outcome: IntegrationDeliveryOutcome; }) => Effect.Effect; @@ -120,6 +125,7 @@ export const runIntegrationDeliveryWorkerPoll = ({ }); const result: { readonly errorTag?: string; + readonly externalResourceDrafts?: readonly IntegrationExternalResourceDraft[]; readonly httpStatus?: number; readonly outcome: IntegrationDeliveryOutcome; } = @@ -158,6 +164,12 @@ export const runIntegrationDeliveryWorkerPoll = ({ ...(response.httpStatus === undefined ? {} : { httpStatus: response.httpStatus }), + ...(response.externalResourceDrafts === undefined + ? {} + : { + externalResourceDrafts: + response.externalResourceDrafts, + }), outcome: { _tag: "Succeeded" } as const, }), }) @@ -167,6 +179,11 @@ export const runIntegrationDeliveryWorkerPoll = ({ ...(result.errorTag === undefined ? {} : { errorTag: result.errorTag }), + ...(result.externalResourceDrafts === undefined + ? {} + : { + externalResourceDrafts: result.externalResourceDrafts, + }), ...(result.httpStatus === undefined ? {} : { httpStatus: result.httpStatus }), diff --git a/integrations/core/src/integration-persistence.test.ts b/integrations/core/src/integration-persistence.test.ts index 6cad0db0..8e99f2ce 100644 --- a/integrations/core/src/integration-persistence.test.ts +++ b/integrations/core/src/integration-persistence.test.ts @@ -14,6 +14,7 @@ import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { + IntegrationCapabilityKey, IntegrationEventRecorder, IntegrationProviderKey, } from "./integration-contracts"; @@ -24,40 +25,45 @@ const TestLayer = IntegrationEventRecorderLive.pipe( Layer.provideMerge(Database.PgliteDatabaseLive) ); -const seedRoute = Effect.gen(function* () { - const db = yield* currentDb; - const organizationId = yield* WorkspaceId.generate; - const connectionId = yield* IntegrationConnectionId.generate; - const routeId = yield* IntegrationRouteId.generate; - yield* db.insert(schema.organizationTable).values({ - createdAt: new Date(), - id: organizationId, - name: "Integration persistence test", - slug: organizationId, - }); - yield* db.insert(schema.integrationConnectionTable).values({ - credentialGeneration: 1, - credentialsCiphertext: "encrypted-test-value", - id: connectionId, - lifecycle: "active", - name: "Test endpoint", - organizationId, - provider: IntegrationProviderKey.make("webhook"), - safeDisplayMetadata: { hostname: "example.com" }, - }); - yield* db.insert(schema.integrationRouteTable).values({ - capabilityKey: "events.post", - configVersion: 1, - connectionId, - enabled: true, - eventTypes: ["feedback.post.created"], - id: routeId, - organizationId, - providerConfig: {}, - safeDisplayMetadata: {}, +const seedRoute = ({ + capabilityKey = "events.post", +}: { + readonly capabilityKey?: string; +} = {}) => + Effect.gen(function* () { + const db = yield* currentDb; + const organizationId = yield* WorkspaceId.generate; + const connectionId = yield* IntegrationConnectionId.generate; + const routeId = yield* IntegrationRouteId.generate; + yield* db.insert(schema.organizationTable).values({ + createdAt: new Date(), + id: organizationId, + name: "Integration persistence test", + slug: organizationId, + }); + yield* db.insert(schema.integrationConnectionTable).values({ + credentialGeneration: 1, + credentialsCiphertext: "encrypted-test-value", + id: connectionId, + lifecycle: "active", + name: "Test endpoint", + organizationId, + provider: IntegrationProviderKey.make("webhook"), + safeDisplayMetadata: { hostname: "example.com" }, + }); + yield* db.insert(schema.integrationRouteTable).values({ + capabilityKey: IntegrationCapabilityKey.make(capabilityKey), + configVersion: 1, + connectionId, + enabled: true, + eventTypes: ["feedback.post.created"], + id: routeId, + organizationId, + providerConfig: {}, + safeDisplayMetadata: {}, + }); + return { connectionId, organizationId, routeId }; }); - return { connectionId, organizationId, routeId }; -}); const makePostCreatedEvent = Effect.gen(function* () { const id = yield* IntegrationEventId.generate; @@ -97,7 +103,7 @@ describe("integration persistence", () => { Effect.gen(function* () { const db = yield* currentDb; const recorder = yield* IntegrationEventRecorder; - const route = yield* seedRoute; + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; const event = { ...input.event, @@ -135,10 +141,10 @@ describe("integration persistence", () => { Effect.gen(function* () { const db = yield* currentDb; const recorder = yield* IntegrationEventRecorder; - const repository = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); - const route = yield* seedRoute; + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; const event = { ...input.event, @@ -196,7 +202,7 @@ describe("integration persistence", () => { () => Effect.gen(function* () { const recorder = yield* IntegrationEventRecorder; - const route = yield* seedRoute; + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; yield* transaction( recorder.recordIntegrationEvent({ @@ -204,9 +210,9 @@ describe("integration persistence", () => { }) ); - const kernel = yield* makeIntegrationDeliveryWorkerRepository([ - "other.capability", - ]); + const kernel = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["other.capability"]]]) + ); const unclaimed = yield* kernel.claimDueDeliveries({ leaseDurationMs: 60_000, leaseOwner: "kernel-only", @@ -214,9 +220,9 @@ describe("integration persistence", () => { }); expect(unclaimed).toHaveLength(0); - const webhookKernel = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); + const webhookKernel = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); const claimed = yield* webhookKernel.claimDueDeliveries({ leaseDurationMs: 60_000, leaseOwner: "webhook-kernel", @@ -226,16 +232,71 @@ describe("integration persistence", () => { }) ); + it.effect( + "claims only provider-owned capability keys and rejects unknown or cross-provider keys", + () => + Effect.gen(function* () { + const recorder = yield* IntegrationEventRecorder; + const valid = yield* seedRoute(); + const crossProvider = yield* seedRoute({ capabilityKey: "commands" }); + const unknown = yield* seedRoute({ + capabilityKey: "unknown.capability", + }); + const validEvent = yield* makePostCreatedEvent; + const crossProviderEvent = yield* makePostCreatedEvent; + const unknownEvent = yield* makePostCreatedEvent; + yield* transaction( + recorder.recordIntegrationEvent({ + event: { + ...validEvent.event, + organizationId: valid.organizationId, + }, + }) + ); + yield* transaction( + recorder.recordIntegrationEvent({ + event: { + ...crossProviderEvent.event, + organizationId: crossProvider.organizationId, + }, + }) + ); + yield* transaction( + recorder.recordIntegrationEvent({ + event: { + ...unknownEvent.event, + organizationId: unknown.organizationId, + }, + }) + ); + + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([ + ["webhook", ["events.post"]], + ["slack", ["commands"]], + ]) + ); + const claimed = yield* repository.claimDueDeliveries({ + leaseDurationMs: 60_000, + leaseOwner: "provider-aware", + limit: 10, + }); + + expect(claimed).toHaveLength(1); + expect(claimed[0]?.input.route.id).toBe(valid.routeId); + }) + ); + it.effect( "requeues expired leases and preserves the stable delivery ID", () => Effect.gen(function* () { const db = yield* currentDb; const recorder = yield* IntegrationEventRecorder; - const repository = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); - const route = yield* seedRoute; + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; const event = { ...input.event, @@ -316,10 +377,10 @@ describe("integration persistence", () => { Effect.gen(function* () { const db = yield* currentDb; const recorder = yield* IntegrationEventRecorder; - const repository = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); - const route = yield* seedRoute; + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; yield* transaction( recorder.recordIntegrationEvent({ @@ -366,10 +427,10 @@ describe("integration persistence", () => { () => Effect.gen(function* () { const recorder = yield* IntegrationEventRecorder; - const repository = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); - const route = yield* seedRoute; + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; yield* transaction( recorder.recordIntegrationEvent({ @@ -401,10 +462,10 @@ describe("integration persistence", () => { Effect.gen(function* () { const db = yield* currentDb; const recorder = yield* IntegrationEventRecorder; - const repository = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); - const route = yield* seedRoute; + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); + const route = yield* seedRoute(); for (let count = 0; count < 11; count++) { const input = yield* makePostCreatedEvent; yield* transaction( @@ -461,10 +522,10 @@ describe("integration persistence", () => { Effect.gen(function* () { const db = yield* currentDb; const recorder = yield* IntegrationEventRecorder; - const repository = yield* makeIntegrationDeliveryWorkerRepository([ - "events.post", - ]); - const route = yield* seedRoute; + const repository = yield* makeIntegrationDeliveryWorkerRepository( + new Map([["webhook", ["events.post"]]]) + ); + const route = yield* seedRoute(); const input = yield* makePostCreatedEvent; const event = { ...input.event, diff --git a/integrations/core/src/oauth-state.ts b/integrations/core/src/oauth-state.ts index 0b077f6b..202b6de5 100644 --- a/integrations/core/src/oauth-state.ts +++ b/integrations/core/src/oauth-state.ts @@ -1,3 +1,4 @@ +import { IntegrationConnectionId, WorkspaceId } from "@feeblo/id"; import * as Schema from "effect/Schema"; /** @@ -6,8 +7,8 @@ import * as Schema from "effect/Schema"; * by the encrypted nonce stored on the pending connection row. */ export const IntegrationOAuthState = Schema.Struct({ - connectionId: Schema.String, - organizationId: Schema.String, + connectionId: IntegrationConnectionId.schema, + organizationId: WorkspaceId.schema, nonce: Schema.String, }); export type IntegrationOAuthState = Schema.Schema.Type< diff --git a/integrations/core/src/provider-registry.test.ts b/integrations/core/src/provider-registry.test.ts index 0a1842b8..671fa4e5 100644 --- a/integrations/core/src/provider-registry.test.ts +++ b/integrations/core/src/provider-registry.test.ts @@ -1,8 +1,9 @@ -import { describe, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Schema from "effect/Schema"; +import { describe, expect, it } from "vitest"; import { + IntegrationCapabilityKey, IntegrationProviderKey, type IntegrationProviderRegistration, makeIntegrationProviderRegistry, @@ -13,7 +14,7 @@ const testProviderKey = IntegrationProviderKey.make("test-provider"); const webhookRegistration = ({ handlers = [ { - capabilityKey: "events.post", + capabilityKey: IntegrationCapabilityKey.make("events.post"), deliver: () => Effect.succeed({}), }, ], @@ -26,7 +27,7 @@ const webhookRegistration = ({ inboundHandlers, manifest: manifest ?? { capabilities: [ - { configVersion: 1, direction: "outbound", key: "events.post" }, + { configVersion: 1, direction: "outbound", key: IntegrationCapabilityKey.make("events.post") }, ], connectionMode: "none", displayName: "Webhook", @@ -36,88 +37,74 @@ const webhookRegistration = ({ }); describe("makeIntegrationProviderRegistry", () => { - it.effect( - "provides an advertised capability handler after startup validation", - () => - Effect.gen(function* () { - const registry = yield* makeIntegrationProviderRegistry([ - webhookRegistration(), - ]); + it("provides an advertised capability handler after startup validation", () => { + const registry = Effect.runSync( + makeIntegrationProviderRegistry([webhookRegistration()]) + ); - expect( - registry.getHandler({ - capabilityKey: "events.post", - provider: testProviderKey, - }) - ).toBeDefined(); - expect(registry.manifests).toHaveLength(1); + expect( + registry.getHandler({ + capabilityKey: "events.post", + provider: testProviderKey, }) - ); + ).toBeDefined(); + expect(registry.manifests).toHaveLength(1); + }); - it.effect( - "rejects an advertised capability without a configuration schema", - () => - Effect.gen(function* () { - const exit = yield* Effect.exit( - makeIntegrationProviderRegistry([ - webhookRegistration({ routeConfigurationSchemas: new Map() }), - ]) - ); + it("rejects an advertised capability without a configuration schema", () => { + const exit = Effect.runSyncExit( + makeIntegrationProviderRegistry([ + webhookRegistration({ routeConfigurationSchemas: new Map() }), + ]) + ); - expect(Exit.isFailure(exit)).toBe(true); - }) - ); + expect(Exit.isFailure(exit)).toBe(true); + }); - it.effect("rejects duplicate static provider registrations", () => - Effect.gen(function* () { - const registration = webhookRegistration(); - const exit = yield* Effect.exit( - makeIntegrationProviderRegistry([registration, registration]) - ); + it("rejects duplicate static provider registrations", () => { + const registration = webhookRegistration(); + const exit = Effect.runSyncExit( + makeIntegrationProviderRegistry([registration, registration]) + ); - expect(Exit.isFailure(exit)).toBe(true); - }) - ); + expect(Exit.isFailure(exit)).toBe(true); + }); - it.effect( - "rejects a handler for a capability not advertised in the manifest", - () => - Effect.gen(function* () { - const registration = webhookRegistration(); - const exit = yield* Effect.exit( - makeIntegrationProviderRegistry([ + it("rejects a handler for a capability not advertised in the manifest", () => { + const registration = webhookRegistration(); + const exit = Effect.runSyncExit( + makeIntegrationProviderRegistry([ + { + ...registration, + handlers: [ { - ...registration, - handlers: [ - { - capabilityKey: "events.post", - deliver: () => Effect.succeed({}), - }, - ], - manifest: { - ...registration.manifest, - capabilities: [], - }, + capabilityKey: IntegrationCapabilityKey.make("events.post"), + deliver: () => Effect.succeed({}), }, - ]) - ); + ], + manifest: { + ...registration.manifest, + capabilities: [], + }, + }, + ]) + ); - expect(Exit.isFailure(exit)).toBe(true); - }) - ); + expect(Exit.isFailure(exit)).toBe(true); + }); it("provides an inbound capability handler after startup validation", () => { const registration = webhookRegistration({ inboundHandlers: [ { - capabilityKey: "commands", + capabilityKey: IntegrationCapabilityKey.make("commands"), handle: () => Effect.succeed({ body: {}, status: 200 }), }, ], manifest: { capabilities: [ - { configVersion: 1, direction: "outbound", key: "events.post" }, - { configVersion: 1, direction: "inbound", key: "commands" }, + { configVersion: 1, direction: "outbound", key: IntegrationCapabilityKey.make("events.post") }, + { configVersion: 1, direction: "inbound", key: IntegrationCapabilityKey.make("commands") }, ], connectionMode: "none", displayName: "Webhook", @@ -149,7 +136,7 @@ describe("makeIntegrationProviderRegistry", () => { manifest: { ...registration.manifest, capabilities: [ - { configVersion: 1, direction: "inbound", key: "commands" }, + { configVersion: 1, direction: "inbound", key: IntegrationCapabilityKey.make("commands") }, ], }, routeConfigurationSchemas: new Map([["commands", Schema.Json]]), diff --git a/integrations/core/src/provider-registry.ts b/integrations/core/src/provider-registry.ts index c2d67876..057c40d7 100644 --- a/integrations/core/src/provider-registry.ts +++ b/integrations/core/src/provider-registry.ts @@ -8,7 +8,7 @@ import type { } from "./integration-contracts"; /** Startup failure when a statically registered provider does not meet its manifest contract. */ -export class IntegrationProviderRegistryValidationError extends Schema.TaggedErrorClass()( +export class IntegrationProviderRegistryValidationError extends Schema.TaggedError()( "IntegrationProviderRegistryValidationError", { capabilityKey: Schema.optionalKey(Schema.String), diff --git a/integrations/core/src/request-signature.ts b/integrations/core/src/request-signature.ts index e3d8dd08..8539b32a 100644 --- a/integrations/core/src/request-signature.ts +++ b/integrations/core/src/request-signature.ts @@ -2,7 +2,7 @@ import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; /** Failure verifying a provider request signature (freshness window, scheme, or cryptographic match). */ -export class IntegrationRequestSignatureError extends Schema.TaggedErrorClass()( +export class IntegrationRequestSignatureError extends Schema.TaggedError()( "IntegrationRequestSignatureError", { reason: Schema.String } ) {} diff --git a/integrations/discord/package.json b/integrations/discord/package.json index da88fa35..adf82057 100644 --- a/integrations/discord/package.json +++ b/integrations/discord/package.json @@ -22,7 +22,7 @@ "effect": "catalog:" }, "devDependencies": { - "@effect/vitest": "4.0.0-beta.94", + "@effect/vitest": "catalog:", "@feeblo/config": "workspace:*", "@feeblo/id": "workspace:*", "@types/node": "catalog:", diff --git a/integrations/discord/src/discord-errors.ts b/integrations/discord/src/discord-errors.ts index 05ac776e..40a3f168 100644 --- a/integrations/discord/src/discord-errors.ts +++ b/integrations/discord/src/discord-errors.ts @@ -20,7 +20,7 @@ import type { } from "@feeblo/integration-core"; /** Failure parsing a verified Discord interaction request body into its typed payload. */ -export class DiscordInboundPayloadError extends Schema.TaggedErrorClass()( +export class DiscordInboundPayloadError extends Schema.TaggedError()( "DiscordInboundPayloadError", { reason: Schema.String } ) {} diff --git a/integrations/discord/src/discord-manifest.ts b/integrations/discord/src/discord-manifest.ts index d6c92cd2..c71a5ce0 100644 --- a/integrations/discord/src/discord-manifest.ts +++ b/integrations/discord/src/discord-manifest.ts @@ -8,6 +8,10 @@ import * as Schema from "effect/Schema"; /** Provider key owned by the Discord adapter, outside the provider-neutral kernel. */ export const discordProviderKey = IntegrationProviderKey.make("discord"); +export const discordChannelNotificationsCapabilityKey = + IntegrationCapabilityKey.make("channel.notifications"); +export const discordInteractionsCapabilityKey = + IntegrationCapabilityKey.make("interactions"); /** Discord OAuth scopes requested during server installation. */ export const DISCORD_OAUTH_SCOPES = [ @@ -92,12 +96,12 @@ export const discordProviderManifest = IntegrationProviderManifest.make({ connectionMode: "oauth2", capabilities: [ { - key: IntegrationCapabilityKey.make("channel.notifications"), + key: discordChannelNotificationsCapabilityKey, direction: "outbound", configVersion: 1, }, { - key: IntegrationCapabilityKey.make("interactions"), + key: discordInteractionsCapabilityKey, direction: "inbound", configVersion: 1, }, diff --git a/integrations/discord/src/discord-provider-registration.test.ts b/integrations/discord/src/discord-provider-registration.test.ts index 0b85aacd..3ccb7e49 100644 --- a/integrations/discord/src/discord-provider-registration.test.ts +++ b/integrations/discord/src/discord-provider-registration.test.ts @@ -17,7 +17,7 @@ import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Redacted from "effect/Redacted"; import type { DiscordApiClient } from "./discord-api"; -import { discordProviderKey } from "./discord-manifest"; +import { discordChannelNotificationsCapabilityKey, discordProviderKey } from "./discord-manifest"; import { makeDiscordProviderRegistration } from "./discord-provider-registration"; // Discord interaction signatures are Ed25519; the test keypair is generated @@ -103,7 +103,7 @@ const deliveryInput = ( version: 1, }, route: { - capabilityKey: "channel.notifications", + capabilityKey: discordChannelNotificationsCapabilityKey, configVersion: 1, connectionId: asLegid(IntegrationConnectionId)("conn_1"), enabled: true, diff --git a/integrations/discord/src/discord-provider-registration.ts b/integrations/discord/src/discord-provider-registration.ts index f7272be8..ae6faeb5 100644 --- a/integrations/discord/src/discord-provider-registration.ts +++ b/integrations/discord/src/discord-provider-registration.ts @@ -24,6 +24,8 @@ import { DiscordChannelNotificationRouteConfiguration, DiscordConnectionConfiguration, DiscordInboundRouteConfiguration, + discordChannelNotificationsCapabilityKey, + discordInteractionsCapabilityKey, discordProviderKey, discordProviderManifest, } from "./discord-manifest"; @@ -105,7 +107,7 @@ const makeDiscordInteractionsHandler = ({ }: { readonly publicKey: string; }): IntegrationInboundCapabilityHandler => ({ - capabilityKey: "interactions", + capabilityKey: discordInteractionsCapabilityKey, handle: (input: IntegrationInboundRequest) => Effect.gen(function* () { const verified = yield* Effect.result( @@ -153,7 +155,7 @@ export const makeDiscordProviderRegistration = ({ readonly publicKey: string; }): IntegrationProviderRegistration => { const channelNotificationsHandler = { - capabilityKey: "channel.notifications" as const, + capabilityKey: discordChannelNotificationsCapabilityKey, deliver: (input: IntegrationProviderDeliveryInput) => Effect.gen(function* () { if (input.event.type !== "feedback.post.created") { @@ -222,8 +224,8 @@ export const makeDiscordProviderRegistration = ({ inboundHandlers: [makeDiscordInteractionsHandler({ publicKey })], manifest: discordProviderManifest, routeConfigurationSchemas: new Map([ - ["channel.notifications", DiscordChannelNotificationRouteConfiguration], - ["interactions", DiscordInboundRouteConfiguration], + [discordChannelNotificationsCapabilityKey, DiscordChannelNotificationRouteConfiguration], + [discordInteractionsCapabilityKey, DiscordInboundRouteConfiguration], ]), }; }; diff --git a/integrations/github/package.json b/integrations/github/package.json new file mode 100644 index 00000000..8a2d404d --- /dev/null +++ b/integrations/github/package.json @@ -0,0 +1,32 @@ +{ + "name": "@feeblo/integration-github", + "type": "module", + "version": "0.0.1", + "private": true, + "exports": { + ".": "./src/index.ts", + "./manifest": "./src/github-manifest.ts", + "./credentials": "./src/github-credentials.ts", + "./signature": "./src/github-signature.ts", + "./inbound-schema": "./src/github-inbound-schema.ts", + "./provider-registration": "./src/github-provider-registration.ts" + }, + "scripts": { + "check-types": "tsc --noEmit", + "test": "vitest run" + }, + "dependencies": { + "@distilled.cloud/github": "1.0.0-rc.4", + "@feeblo/integration-core": "workspace:*", + "effect": "catalog:", + "jose": "catalog:" + }, + "devDependencies": { + "@effect/vitest": "catalog:", + "@feeblo/config": "workspace:*", + "@feeblo/id": "workspace:*", + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/integrations/github/src/github-api.test.ts b/integrations/github/src/github-api.test.ts new file mode 100644 index 00000000..e5c92059 --- /dev/null +++ b/integrations/github/src/github-api.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Redacted from "effect/Redacted"; +import * as Schema from "effect/Schema"; +import * as Headers from "effect/unstable/http/Headers"; +import { + classifyGitHubApiError, + GitHubInstallationAccessToken, + GitHubInstallationRepositories, + GitHubIssue, + GitHubUserInstallations, + makeGitHubApiClient, + renderGitHubIssueBacklinkComment, +} from "./github-api"; + +describe("GitHub App API response schemas", () => { + it.effect( + "decodes issues, installation tokens, and paginated installation repositories", + () => + Effect.gen(function* () { + const [issue, token, repositories] = yield* Effect.all([ + Schema.decodeUnknownEffect(GitHubIssue)({ + html_url: "https://github.com/acme/feedback/issues/7", + id: 7, + node_id: "I_7", + number: 7, + state: "open", + title: "Dark mode", + }), + Schema.decodeUnknownEffect(GitHubInstallationAccessToken)({ + expires_at: "2030-01-01T00:00:00Z", + token: "ghs_installation_token", + }), + Schema.decodeUnknownEffect(GitHubInstallationRepositories)({ + repositories: [ + { + full_name: "acme/feedback", + id: 1, + name: "feedback", + owner: { login: "acme" }, + private: true, + }, + ], + total_count: 1, + }), + ]); + expect(issue.number).toBe(7); + expect(token.token).toBe("ghs_installation_token"); + expect(repositories.repositories[0]?.full_name).toBe("acme/feedback"); + }) + ); + + it.effect("rejects malformed App installation payloads", () => + Effect.gen(function* () { + const result = yield* Effect.exit( + Schema.decodeUnknownEffect(GitHubUserInstallations)({ + installations: [{ id: "not-a-number" }], + total_count: 1, + }) + ); + expect(Exit.isFailure(result)).toBe(true); + }) + ); +}); + +describe("GitHub API failure classification", () => { + it("only treats a 403 as rate limited when GitHub reports rate limiting", () => { + expect( + classifyGitHubApiError( + { status: 403, headers: Headers.fromInput({}) }, + "repository listing" + )._tag + ).toBe("IntegrationProviderPermanentRejection"); + expect( + classifyGitHubApiError( + { + status: 403, + headers: Headers.fromInput({ "x-ratelimit-remaining": "0" }), + }, + "repository listing" + )._tag + ).toBe("IntegrationProviderRateLimitedError"); + }); +}); + +describe("GitHub App installation id validation", () => { + it.effect( + "rejects a non-numeric installation id before minting a token", + () => + Effect.gen(function* () { + const client = makeGitHubApiClient(); + const tag = yield* client + .createInstallationAccessToken({ + appJwt: Redacted.make("app-jwt"), + installationId: "not-a-number", + }) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + expect(tag).toBe("IntegrationProviderInvalidConfigurationError"); + }) + ); + + it.effect("rejects a non-numeric installation id before removal", () => + Effect.gen(function* () { + const client = makeGitHubApiClient(); + const tag = yield* client + .deleteInstallation({ + appJwt: Redacted.make("app-jwt"), + installationId: "not-a-number", + }) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + expect(tag).toBe("IntegrationProviderInvalidConfigurationError"); + }) + ); +}); + +describe("GitHub bot backlink comments", () => { + it("renders the feedback-platform backlink as a markdown link", () => { + const backlinkUrl = new URL("https://feeblo.example/post/one"); + expect(renderGitHubIssueBacklinkComment({ backlinkUrl })).toBe( + "The issue is linked to our feedback platform. For feedback and updates, please visit [this link](https://feeblo.example/post/one)" + ); + }); +}); diff --git a/integrations/github/src/github-api.ts b/integrations/github/src/github-api.ts new file mode 100644 index 00000000..91d47af1 --- /dev/null +++ b/integrations/github/src/github-api.ts @@ -0,0 +1,677 @@ +import * as GitHub from "@distilled.cloud/github"; +import { + IntegrationProviderAuthenticationError, + IntegrationProviderInvalidConfigurationError, + IntegrationProviderPermanentRejection, + IntegrationProviderRateLimitedError, + IntegrationProviderTemporaryFailure, +} from "@feeblo/integration-core"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Predicate from "effect/Predicate"; +import * as Redacted from "effect/Redacted"; +import * as Schema from "effect/Schema"; +import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"; +import * as Headers from "effect/unstable/http/Headers"; +import * as HttpClient from "effect/unstable/http/HttpClient"; +import * as HttpClientError from "effect/unstable/http/HttpClientError"; +import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"; +import type { GitHubApiFailure } from "./github-errors"; +import { githubProviderKey } from "./github-manifest"; + +/** GitHub's public REST API origin. */ +export const GITHUB_API_BASE_URL = "https://api.github.com"; +/** GitHub endpoint used only to verify the administrator completing setup. */ +export const GITHUB_OAUTH_TOKEN_URL = + "https://github.com/login/oauth/access_token"; +/** Per-request upper bound for GitHub REST API calls. */ +export const GITHUB_API_REQUEST_TIMEOUT_MS = 10_000; +/** GitHub App installation ids are positive decimal integers. */ +const GITHUB_INSTALLATION_ID_PATTERN = /^[1-9]\d*$/; + +/** A short-lived token minted for one GitHub App installation. */ +export const GitHubInstallationAccessToken = Schema.Struct({ + expires_at: Schema.DateFromString, + token: Schema.NonEmptyString, +}); +export interface GitHubInstallationAccessToken + extends Schema.Schema.Type {} + +/** One-time user token returned during GitHub App installer verification. */ +export const GitHubUserAccessToken = Schema.Struct({ + access_token: Schema.NonEmptyString, + token_type: Schema.String, +}); +export interface GitHubUserAccessToken + extends Schema.Schema.Type {} + +/** GitHub account associated with an app installation. */ +export const GitHubInstallationAccount = Schema.Struct({ + id: Schema.Number, + login: Schema.NonEmptyString, + type: Schema.Literals(["Organization", "User"]), +}); +export interface GitHubInstallationAccount + extends Schema.Schema.Type {} + +/** Installation facts necessary to verify setup and route globally delivered webhooks. */ +export const GitHubUserInstallation = Schema.Struct({ + account: Schema.NullOr(GitHubInstallationAccount), + id: Schema.Number, + repository_selection: Schema.Literals(["all", "selected"]), + suspended_at: Schema.NullOr(Schema.DateFromString), +}); +export interface GitHubUserInstallation + extends Schema.Schema.Type {} + +/** GitHub's user-installations response envelope. */ +export const GitHubUserInstallations = Schema.Struct({ + installations: Schema.Array(GitHubUserInstallation), + total_count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), +}); +export interface GitHubUserInstallations + extends Schema.Schema.Type {} + +/** A repository accessible to an authenticated GitHub App installation. */ +export const GitHubRepository = Schema.Struct({ + full_name: Schema.NonEmptyString, + id: Schema.Number, + name: Schema.NonEmptyString, + owner: Schema.Struct({ login: Schema.NonEmptyString }), + private: Schema.Boolean, +}); +export interface GitHubRepository + extends Schema.Schema.Type {} + +/** Paginated repository response returned to a GitHub App installation. */ +export const GitHubInstallationRepositories = Schema.Struct({ + repositories: Schema.Array(GitHubRepository), + total_count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), +}); +export interface GitHubInstallationRepositories + extends Schema.Schema.Type {} + +/** Safe issue fields used to persist a normalized external resource link. */ +export const GitHubIssue = Schema.Struct({ + html_url: Schema.URLFromString, + id: Schema.Number, + node_id: Schema.String, + number: Schema.Int, + state: Schema.Literals(["open", "closed"]), + title: Schema.String, +}); +export interface GitHubIssue extends Schema.Schema.Type {} + +/** Formats the bot-authored comment linking a GitHub issue back to its Feeblo post. */ +export const renderGitHubIssueBacklinkComment = ({ + backlinkUrl, +}: { + readonly backlinkUrl: URL; +}): string => + `The issue is linked to our feedback platform. For feedback and updates, please visit [this link](${backlinkUrl.href})`; + +/** Maps GitHub transport statuses into integration kernel failures. */ +export const classifyGitHubApiError = ( + response: { + readonly status?: number; + readonly headers?: Headers.Headers; + }, + context: string +): GitHubApiFailure => { + const status = response.status; + if (status === 401) { + return new IntegrationProviderAuthenticationError({ + message: `GitHub rejected authentication during ${context}`, + provider: githubProviderKey, + httpStatus: status, + }); + } + const isRateLimited403 = + status === 403 && + response.headers !== undefined && + (Headers.get(response.headers, "x-ratelimit-remaining").pipe( + Option.getOrUndefined + ) === "0" || + Headers.has(response.headers, "retry-after")); + if (isRateLimited403 || status === 429) { + return new IntegrationProviderRateLimitedError({ + message: `GitHub rate limited ${context}`, + provider: githubProviderKey, + ...(status === undefined ? {} : { httpStatus: status }), + }); + } + if (status !== undefined && status >= 500) { + return new IntegrationProviderTemporaryFailure({ + message: `GitHub temporarily failed during ${context}`, + provider: githubProviderKey, + httpStatus: status, + }); + } + if (status === 404 || status === 410) { + return new IntegrationProviderInvalidConfigurationError({ + message: `GitHub repository, installation, or issue was not found during ${context}`, + provider: githubProviderKey, + httpStatus: status, + }); + } + return new IntegrationProviderPermanentRejection({ + message: `GitHub rejected ${context}`, + provider: githubProviderKey, + ...(status === undefined ? {} : { httpStatus: status }), + }); +}; + +/** Direct, schema-decoding adapter for the GitHub REST API. */ +export interface GitHubApiClient { + /** Mints an ephemeral installation token with an App JWT. */ + readonly createInstallationAccessToken: (input: { + readonly appJwt: Redacted.Redacted; + readonly installationId: string; + }) => Effect.Effect; + /** Creates an issue as the GitHub App installation bot. */ + readonly createIssue: (input: { + readonly accessToken: Redacted.Redacted; + readonly body: string; + readonly repositoryName: string; + readonly repositoryOwner: string; + readonly title: string; + }) => Effect.Effect; + /** Posts a bot-authored Feeblo backlink comment on an existing issue. */ + readonly createIssueBacklinkComment: (input: { + readonly accessToken: Redacted.Redacted; + readonly backlinkUrl: URL; + readonly issueNumber: number; + readonly repositoryName: string; + readonly repositoryOwner: string; + }) => Effect.Effect; + /** Uninstalls the GitHub App from one account using App authentication. */ + readonly deleteInstallation: (input: { + readonly appJwt: Redacted.Redacted; + readonly installationId: string; + }) => Effect.Effect; + /** Exchanges the callback code only to prove the setup user can access the installation. */ + readonly exchangeUserAccessToken: (input: { + readonly clientId: string; + readonly clientSecret: Redacted.Redacted; + readonly code: string; + }) => Effect.Effect; + /** Resolves a linked issue before Feeblo persists its provider-neutral resource. */ + readonly getIssue: (input: { + readonly accessToken: Redacted.Redacted; + readonly issueNumber: number; + readonly repositoryName: string; + readonly repositoryOwner: string; + }) => Effect.Effect; + /** Returns one repository page visible to an installation token. */ + readonly listInstallationRepositories: (input: { + readonly accessToken: Redacted.Redacted; + readonly page: number; + }) => Effect.Effect; + /** Lists installations the one-time setup user can access. */ + readonly listUserInstallations: (input: { + readonly accessToken: Redacted.Redacted; + readonly page: number; + }) => Effect.Effect; +} + +/** Extracts the stable `_tag` of a tagged SDK error, if present. */ +const sdkErrorTag = (error: unknown): string | undefined => + Predicate.isObject(error) && "_tag" in error && typeof error._tag === "string" + ? error._tag + : undefined; + +/** Extracts a human-readable message from an SDK error, if present. */ +const sdkErrorMessage = (error: unknown): string | undefined => + Predicate.isObject(error) && + "message" in error && + typeof error.message === "string" + ? error.message + : undefined; + +/** Extracts a server-provided retry hint from an SDK error, if present. */ +const sdkRetryAfterMs = (error: unknown): number | undefined => + Predicate.isObject(error) && + "retryAfter" in error && + Duration.isDuration(error.retryAfter) + ? Duration.toMillis(error.retryAfter) + : undefined; + +/** Maps the Effect-native SDK's typed errors onto the integration kernel failure algebra. */ +const mapSdkError = + (context: string) => + (error: unknown): GitHubApiFailure => { + if (HttpClientError.isHttpClientError(error)) { + return new IntegrationProviderTemporaryFailure({ + message: `GitHub request failed during ${context}`, + provider: githubProviderKey, + }); + } + const detail = sdkErrorMessage(error) ?? `GitHub rejected ${context}`; + switch (sdkErrorTag(error)) { + case "Unauthorized": + return new IntegrationProviderAuthenticationError({ + message: detail, + provider: githubProviderKey, + httpStatus: 401, + }); + case "Forbidden": + return new IntegrationProviderAuthenticationError({ + message: detail, + provider: githubProviderKey, + httpStatus: 403, + }); + case "TooManyRequests": { + const retryAfterMs = sdkRetryAfterMs(error); + return new IntegrationProviderRateLimitedError({ + message: detail, + provider: githubProviderKey, + httpStatus: 429, + ...(retryAfterMs === undefined ? {} : { retryAfterMs }), + }); + } + case "BadRequest": + return new IntegrationProviderPermanentRejection({ + message: detail, + provider: githubProviderKey, + httpStatus: 400, + }); + case "Conflict": + return new IntegrationProviderPermanentRejection({ + message: detail, + provider: githubProviderKey, + httpStatus: 409, + }); + case "UnprocessableEntity": + return new IntegrationProviderPermanentRejection({ + message: detail, + provider: githubProviderKey, + httpStatus: 422, + }); + case "Locked": + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + httpStatus: 423, + }); + case "NotFound": + return new IntegrationProviderInvalidConfigurationError({ + message: detail, + provider: githubProviderKey, + httpStatus: 404, + }); + case "Gone": + return new IntegrationProviderInvalidConfigurationError({ + message: detail, + provider: githubProviderKey, + httpStatus: 410, + }); + case "InternalServerError": + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + httpStatus: 500, + }); + case "BadGateway": + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + httpStatus: 502, + }); + case "ServiceUnavailable": + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + httpStatus: 503, + }); + case "GatewayTimeout": + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + httpStatus: 504, + }); + case "ConfigError": + return new IntegrationProviderInvalidConfigurationError({ + message: detail, + provider: githubProviderKey, + }); + case "UnknownGithubError": + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + }); + case "GithubParseError": + return new IntegrationProviderTemporaryFailure({ + message: `GitHub returned an unparsable response during ${context}`, + provider: githubProviderKey, + }); + default: + return new IntegrationProviderTemporaryFailure({ + message: detail, + provider: githubProviderKey, + }); + } + }; + +/** Provides the SDK's per-request bearer credentials from one redacted token. */ +const credentialsLayer = ( + token: Redacted.Redacted +): Layer.Layer => + Layer.succeed( + GitHub.Credentials, + Effect.succeed({ + token, + apiBaseUrl: GITHUB_API_BASE_URL, + userAgent: GitHub.DEFAULT_USER_AGENT, + }) + ); + +/** + * Runs one generated SDK operation with bearer credentials, no SDK-level + * retries (the durable delivery scheduler owns retry policy), the repository's + * request timeout, and the SDK's typed errors mapped onto the kernel algebra. + */ +const withSdk = ( + token: Redacted.Redacted, + effect: Effect.Effect, + context: string +): Effect.Effect => + effect.pipe( + Effect.provide( + Layer.mergeAll(credentialsLayer(token), FetchHttpClient.layer) + ), + Effect.mapError(mapSdkError(context)), + Effect.timeoutOrElse({ + duration: GITHUB_API_REQUEST_TIMEOUT_MS, + orElse: () => + Effect.fail( + new IntegrationProviderTemporaryFailure({ + message: `GitHub request timed out during ${context}`, + provider: githubProviderKey, + }) + ), + }) + ); + +/** Decodes an SDK response back through one of the repository's stricter schemas. */ +const decodeSdkResponse = + (schema: S, context: string) => + ( + value: unknown + ): Effect.Effect => + Schema.decodeUnknownEffect(schema)(value).pipe( + Effect.mapError( + () => + new IntegrationProviderTemporaryFailure({ + message: `GitHub ${context} response was invalid`, + provider: githubProviderKey, + }) + ) + ); + +/** Validates a GitHub App installation id before it is converted for the SDK. */ +const installationIdToNumber = ( + installationId: string +): Effect.Effect => + GITHUB_INSTALLATION_ID_PATTERN.test(installationId) + ? Effect.succeed(Number(installationId)) + : Effect.fail( + new IntegrationProviderInvalidConfigurationError({ + message: "GitHub App installation id is invalid", + provider: githubProviderKey, + }) + ); + +/** Creates a GitHub adapter backed by the Effect-native @distilled.cloud/github SDK. */ +export const makeGitHubApiClient = (): GitHubApiClient => { + const exchangeUserAccessToken = ({ + clientId, + clientSecret, + code, + }: { + readonly clientId: string; + readonly clientSecret: Redacted.Redacted; + readonly code: string; + }): Effect.Effect => + Effect.gen(function* () { + const httpRequest = yield* HttpClientRequest.bodyJson( + HttpClientRequest.post(GITHUB_OAUTH_TOKEN_URL, { + headers: { accept: "application/json" }, + }), + { + client_id: clientId, + client_secret: Redacted.value(clientSecret), + code, + } + ).pipe( + Effect.mapError( + () => + new IntegrationProviderPermanentRejection({ + message: "GitHub setup user-token request could not be encoded", + provider: githubProviderKey, + }) + ) + ); + const response = yield* HttpClient.execute(httpRequest).pipe( + Effect.provide(FetchHttpClient.layer), + Effect.mapError( + () => + new IntegrationProviderTemporaryFailure({ + message: "GitHub request failed during setup user-token exchange", + provider: githubProviderKey, + }) + ), + Effect.timeoutOrElse({ + duration: GITHUB_API_REQUEST_TIMEOUT_MS, + orElse: () => + Effect.fail( + new IntegrationProviderTemporaryFailure({ + message: + "GitHub request timed out during setup user-token exchange", + provider: githubProviderKey, + }) + ), + }) + ); + if (response.status < 200 || response.status >= 300) { + return yield* classifyGitHubApiError( + { headers: response.headers, status: response.status }, + "setup user-token exchange" + ); + } + const body = yield* response.json.pipe( + Effect.mapError( + () => + new IntegrationProviderPermanentRejection({ + message: + "GitHub returned an invalid response during setup user-token exchange", + provider: githubProviderKey, + httpStatus: response.status, + }) + ) + ); + return yield* decodeSdkResponse( + GitHubUserAccessToken, + "setup user-token exchange" + )(body); + }); + + return { + createIssueBacklinkComment: ({ + accessToken, + backlinkUrl, + issueNumber, + repositoryName, + repositoryOwner, + }) => + withSdk( + accessToken, + GitHub.Retry.none( + GitHub.Services.issues.createComment({ + owner: repositoryOwner, + repo: repositoryName, + issue_number: issueNumber, + body: renderGitHubIssueBacklinkComment({ backlinkUrl }), + }) + ), + "issue backlink comment" + ).pipe(Effect.asVoid), + createIssue: ({ + accessToken, + body, + repositoryName, + repositoryOwner, + title, + }) => + withSdk( + accessToken, + GitHub.Retry.none( + GitHub.Services.issues.create({ + owner: repositoryOwner, + repo: repositoryName, + title, + body, + }) + ), + "issue creation" + ).pipe( + Effect.flatMap((issue) => + decodeSdkResponse(GitHubIssue, "issue creation")(issue) + ) + ), + createInstallationAccessToken: ({ appJwt, installationId }) => + installationIdToNumber(installationId).pipe( + Effect.flatMap((installation_id) => + withSdk( + appJwt, + GitHub.Retry.none( + GitHub.Services.apps.createInstallationAccessToken({ + installation_id, + }) + ), + "installation token creation" + ).pipe( + Effect.flatMap((token) => + decodeSdkResponse( + GitHubInstallationAccessToken, + "installation token creation" + )({ + expires_at: token.expires_at, + token: token.token, + }) + ) + ) + ) + ), + deleteInstallation: ({ appJwt, installationId }) => + installationIdToNumber(installationId).pipe( + Effect.flatMap((installation_id) => + withSdk( + appJwt, + GitHub.Retry.none( + GitHub.Services.apps.deleteInstallation({ + installation_id, + }) + ), + "installation removal" + ).pipe( + Effect.catchIf( + (error) => + Schema.is(IntegrationProviderInvalidConfigurationError)( + error + ) && + (error.httpStatus === 404 || error.httpStatus === 410), + () => Effect.void + ) + ) + ) + ), + exchangeUserAccessToken, + getIssue: ({ accessToken, issueNumber, repositoryName, repositoryOwner }) => + withSdk( + accessToken, + GitHub.Retry.none( + GitHub.Services.issues.get({ + owner: repositoryOwner, + repo: repositoryName, + issue_number: issueNumber, + }) + ), + "issue lookup" + ).pipe( + Effect.flatMap((issue) => + decodeSdkResponse(GitHubIssue, "issue lookup")(issue) + ) + ), + listInstallationRepositories: ({ accessToken, page }) => + withSdk( + accessToken, + GitHub.Retry.none( + GitHub.Services.apps.listReposAccessibleToInstallation({ + per_page: 100, + page, + }) + ), + "installation repository listing" + ).pipe( + Effect.flatMap((response) => + decodeSdkResponse( + GitHubInstallationRepositories, + "installation repository listing" + )({ + repositories: response.repositories.map((repository) => ({ + full_name: repository.full_name, + id: repository.id, + name: repository.name, + owner: { login: repository.owner.login }, + private: repository.private, + })), + total_count: response.total_count, + }) + ) + ), + listUserInstallations: ({ accessToken, page }) => + withSdk( + accessToken, + GitHub.Retry.none( + GitHub.Services.apps.listInstallationsForAuthenticatedUser({ + per_page: 100, + page, + }) + ), + "setup user installation listing" + ).pipe( + Effect.flatMap((response) => + decodeSdkResponse( + GitHubUserInstallations, + "setup user installation listing" + )({ + installations: response.installations.map((installation) => { + const account = installation.account; + if (account === null) { + return { + account: null, + id: installation.id, + repository_selection: installation.repository_selection, + suspended_at: installation.suspended_at, + }; + } + const isUserAccount = "login" in account; + return { + account: { + id: account.id, + login: isUserAccount ? account.login : account.slug, + type: isUserAccount ? account.type : "Organization", + }, + id: installation.id, + repository_selection: installation.repository_selection, + suspended_at: installation.suspended_at, + }; + }), + total_count: response.total_count, + }) + ) + ), + }; +}; diff --git a/integrations/github/src/github-app-auth.test.ts b/integrations/github/src/github-app-auth.test.ts new file mode 100644 index 00000000..afb29cff --- /dev/null +++ b/integrations/github/src/github-app-auth.test.ts @@ -0,0 +1,160 @@ +import { generateKeyPairSync } from "node:crypto"; +import { describe, expect, it } from "@effect/vitest"; +import * as Clock from "effect/Clock"; +import * as Effect from "effect/Effect"; +import * as Redacted from "effect/Redacted"; +import * as jose from "jose"; +import type { GitHubApiClient } from "./github-api"; +import { + createGitHubAppJwt, + makeGitHubInstallationTokenResolver, +} from "./github-app-auth"; + +const makePrivateKey = () => + Effect.tryPromise(() => + jose + .generateKeyPair("RS256", { extractable: true }) + .then((keys) => jose.exportPKCS8(keys.privateKey)) + ); + +describe("GitHub App authentication", () => { + it.effect( + "creates a short-lived RS256 App JWT with GitHub-required claims", + () => + Effect.gen(function* () { + const privateKey = yield* makePrivateKey(); + const now = new Date("2030-01-01T00:00:00Z"); + const token = yield* createGitHubAppJwt({ + appId: "1234", + now, + privateKey: Redacted.make(privateKey), + }); + const protectedHeader = jose.decodeProtectedHeader( + Redacted.value(token) + ); + const claims = jose.decodeJwt(Redacted.value(token)); + expect(protectedHeader.alg).toBe("RS256"); + expect(claims.iss).toBe("1234"); + expect(claims.iat).toBe(Math.floor(now.getTime() / 1000) - 60); + expect(claims.exp).toBeLessThanOrEqual( + Math.floor(now.getTime() / 1000) + 600 + ); + }) + ); + + it.effect( + "accepts the PKCS#1 private key format downloaded from GitHub", + () => + Effect.gen(function* () { + const { privateKey } = generateKeyPairSync("rsa", { + modulusLength: 2048, + privateKeyEncoding: { format: "pem", type: "pkcs1" }, + publicKeyEncoding: { format: "pem", type: "spki" }, + }); + const result = yield* createGitHubAppJwt({ + appId: "1234", + now: new Date("2030-01-01T00:00:00Z"), + privateKey: Redacted.make(privateKey), + }); + expect(jose.decodeProtectedHeader(Redacted.value(result)).alg).toBe( + "RS256" + ); + }) + ); + + it.effect("caches an installation token until its safety skew", () => + Effect.gen(function* () { + const privateKey = yield* makePrivateKey(); + const now = new Date("2030-01-01T00:00:00Z"); + let minted = 0; + const apiClient: GitHubApiClient = { + createInstallationAccessToken: () => { + minted += 1; + return Effect.succeed({ + expires_at: new Date(now.getTime() + 60 * 60 * 1000), + token: `ghs_token_${minted}`, + }); + }, + createIssue: () => Effect.die("unused"), + createIssueBacklinkComment: () => Effect.die("unused"), + deleteInstallation: () => Effect.die("unused"), + exchangeUserAccessToken: () => Effect.die("unused"), + getIssue: () => Effect.die("unused"), + listInstallationRepositories: () => Effect.die("unused"), + listUserInstallations: () => Effect.die("unused"), + }; + const resolver = yield* makeGitHubInstallationTokenResolver({ + apiClient, + appId: "1234", + now: () => now, + privateKey: Redacted.make(privateKey), + }); + const [first, second] = yield* Effect.all([ + resolver.getInstallationAccessToken({ installationId: "9" }), + resolver.getInstallationAccessToken({ installationId: "9" }), + ]); + expect(Redacted.value(first)).toBe("ghs_token_1"); + expect(Redacted.value(second)).toBe("ghs_token_1"); + expect(minted).toBe(1); + }) + ); + + it.effect( + "mints a fresh installation token after the cached one expires", + () => { + let currentTime = new Date("2030-01-01T00:00:00Z"); + let minted = 0; + const fakeClock: Clock.Clock = { + currentTimeMillis: Effect.sync(() => currentTime.getTime()), + currentTimeMillisUnsafe: () => currentTime.getTime(), + currentTimeNanos: Effect.sync( + () => BigInt(currentTime.getTime()) * 1_000_000n + ), + currentTimeNanosUnsafe: () => + BigInt(currentTime.getTime()) * 1_000_000n, + monotonicTimeNanos: Effect.sync( + () => BigInt(currentTime.getTime()) * 1_000_000n + ), + monotonicTimeNanosUnsafe: () => + BigInt(currentTime.getTime()) * 1_000_000n, + sleep: () => Effect.void, + }; + const apiClient: GitHubApiClient = { + createInstallationAccessToken: () => { + minted += 1; + return Effect.succeed({ + expires_at: new Date(currentTime.getTime() + 60 * 60 * 1000), + token: `ghs_token_${minted}`, + }); + }, + createIssue: () => Effect.die("unused"), + createIssueBacklinkComment: () => Effect.die("unused"), + deleteInstallation: () => Effect.die("unused"), + exchangeUserAccessToken: () => Effect.die("unused"), + getIssue: () => Effect.die("unused"), + listInstallationRepositories: () => Effect.die("unused"), + listUserInstallations: () => Effect.die("unused"), + }; + return Effect.gen(function* () { + const privateKey = yield* makePrivateKey(); + const resolver = yield* makeGitHubInstallationTokenResolver({ + apiClient, + appId: "1234", + now: () => currentTime, + privateKey: Redacted.make(privateKey), + }); + const first = yield* resolver.getInstallationAccessToken({ + installationId: "9", + }); + currentTime = new Date(currentTime.getTime() + 61 * 60 * 1000); + const second = yield* resolver.getInstallationAccessToken({ + installationId: "9", + }); + + expect(Redacted.value(first)).toBe("ghs_token_1"); + expect(Redacted.value(second)).toBe("ghs_token_2"); + expect(minted).toBe(2); + }).pipe(Effect.provideService(Clock.Clock, fakeClock)); + } + ); +}); diff --git a/integrations/github/src/github-app-auth.ts b/integrations/github/src/github-app-auth.ts new file mode 100644 index 00000000..d4aad069 --- /dev/null +++ b/integrations/github/src/github-app-auth.ts @@ -0,0 +1,129 @@ +import { createPrivateKey } from "node:crypto"; +import { + IntegrationProviderInvalidConfigurationError, + IntegrationProviderTemporaryFailure, +} from "@feeblo/integration-core"; +import * as Cache from "effect/Cache"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Redacted from "effect/Redacted"; +import * as jose from "jose"; +import type { GitHubApiClient } from "./github-api"; +import type { GitHubApiFailure } from "./github-errors"; +import { githubProviderKey } from "./github-manifest"; + +/** GitHub permits App JWTs for at most ten minutes. */ +export const GITHUB_APP_JWT_LIFETIME_SECONDS = 9 * 60; +/** Leave room for transit and clock skew before reusing an installation token. */ +export const GITHUB_INSTALLATION_TOKEN_EXPIRY_SKEW_MS = 5 * 60 * 1000; + +/** Signs the short-lived RS256 JWT used solely to mint GitHub App installation tokens. */ +export const createGitHubAppJwt = ({ + appId, + now, + privateKey, +}: { + readonly appId: string; + readonly now: Date; + readonly privateKey: Redacted.Redacted; +}): Effect.Effect< + Redacted.Redacted, + IntegrationProviderInvalidConfigurationError +> => + Effect.tryPromise({ + try: async () => { + const normalizedPrivateKey = createPrivateKey( + Redacted.value(privateKey) + ).export({ format: "pem", type: "pkcs8" }); + const key = await jose.importPKCS8( + normalizedPrivateKey.toString(), + "RS256" + ); + return new jose.SignJWT({}) + .setProtectedHeader({ alg: "RS256", typ: "JWT" }) + .setIssuedAt(Math.floor(now.getTime() / 1000) - 60) + .setIssuer(appId) + .setExpirationTime( + Math.floor(now.getTime() / 1000) + GITHUB_APP_JWT_LIFETIME_SECONDS + ) + .sign(key); + }, + catch: () => + new IntegrationProviderInvalidConfigurationError({ + message: "GitHub App private key is invalid.", + provider: githubProviderKey, + }), + }).pipe(Effect.map(Redacted.make)); + +/** Mints and bounds in-memory reuse of ephemeral installation tokens; tokens never enter persistence. */ +export interface GitHubInstallationTokenResolver { + readonly getInstallationAccessToken: (input: { + readonly installationId: string; + }) => Effect.Effect< + Redacted.Redacted, + GitHubApiFailure | IntegrationProviderInvalidConfigurationError + >; +} + +/** Creates one server-lifetime bounded cache for GitHub App installation tokens. */ +export const makeGitHubInstallationTokenResolver = ({ + apiClient, + appId, + now = () => new Date(), + privateKey, +}: { + readonly apiClient: GitHubApiClient; + readonly appId: string; + readonly now?: () => Date; + readonly privateKey: Redacted.Redacted; +}): Effect.Effect => + Effect.gen(function* () { + const tokenCache = yield* Cache.makeWith( + (installationId: string) => + Effect.gen(function* () { + const appJwt = yield* createGitHubAppJwt({ + appId, + now: now(), + privateKey, + }); + const minted = yield* apiClient.createInstallationAccessToken({ + appJwt, + installationId, + }); + if ( + minted.expires_at.getTime() <= + now().getTime() + GITHUB_INSTALLATION_TOKEN_EXPIRY_SKEW_MS + ) { + return yield* new IntegrationProviderTemporaryFailure({ + message: + "GitHub returned an installation token that expires too soon.", + provider: githubProviderKey, + }); + } + return { + expiresAt: minted.expires_at, + token: Redacted.make(minted.token), + }; + }), + { + capacity: 1000, + timeToLive: (exit) => { + if (Exit.isFailure(exit)) { + return 0; + } + return Math.max( + 0, + exit.value.expiresAt.getTime() - + now().getTime() - + GITHUB_INSTALLATION_TOKEN_EXPIRY_SKEW_MS + ); + }, + } + ); + return { + getInstallationAccessToken: (input) => + Cache.get(tokenCache, input.installationId).pipe( + Effect.map((entry) => entry.token) + ), + }; + }); diff --git a/integrations/github/src/github-credentials.test.ts b/integrations/github/src/github-credentials.test.ts new file mode 100644 index 00000000..085f00b4 --- /dev/null +++ b/integrations/github/src/github-credentials.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Redacted from "effect/Redacted"; +import { + decryptGitHubCredentialMaterial, + encryptGitHubCredentialMaterial, +} from "./github-credentials"; + +const encryptionKey = Redacted.make("0123456789abcdef0123456789abcdef"); + +describe("GitHub credential material", () => { + it.effect("encrypts durable GitHub App installation state", () => + Effect.gen(function* () { + const ciphertext = yield* encryptGitHubCredentialMaterial(encryptionKey, { + installationId: "1234", + installationState: "state-nonce", + }); + expect(ciphertext).not.toContain("state-nonce"); + const credentials = yield* decryptGitHubCredentialMaterial( + encryptionKey, + ciphertext + ); + expect(credentials.installationState).toBe("state-nonce"); + expect(credentials.installationId).toBe("1234"); + }) + ); + + it.effect("rejects malformed encrypted material", () => + Effect.gen(function* () { + const result = yield* Effect.exit( + decryptGitHubCredentialMaterial(encryptionKey, "not-ciphertext") + ); + expect(Exit.isFailure(result)).toBe(true); + }) + ); +}); diff --git a/integrations/github/src/github-credentials.ts b/integrations/github/src/github-credentials.ts new file mode 100644 index 00000000..bcfc5eac --- /dev/null +++ b/integrations/github/src/github-credentials.ts @@ -0,0 +1,49 @@ +import { + decryptIntegrationCredentialMaterial, + encryptIntegrationCredentialMaterial, + type IntegrationCredentialEncryptionError, +} from "@feeblo/integration-core"; +import type * as Effect from "effect/Effect"; +import type * as Redacted from "effect/Redacted"; +import * as Schema from "effect/Schema"; + +/** One encrypted persistence value for GitHub App connection material; no secret belongs in route JSON. */ +export type EncryptedGitHubCredential = string; + +export const GitHubEncryptedCredentialMaterial = Schema.Struct({ + /** GitHub App installation identity is encrypted with its pending setup state. */ + installationId: Schema.optionalKey(Schema.NonEmptyString), + /** One-time state generated before redirecting an administrator to GitHub. */ + installationState: Schema.optionalKey(Schema.NonEmptyString), +}); +export type GitHubEncryptedCredentialMaterial = Schema.Schema.Type< + typeof GitHubEncryptedCredentialMaterial +>; + +export const encryptGitHubCredentialMaterial = ( + encryptionKey: Redacted.Redacted, + credentials: GitHubEncryptedCredentialMaterial +): Effect.Effect< + EncryptedGitHubCredential, + IntegrationCredentialEncryptionError +> => + encryptIntegrationCredentialMaterial( + encryptionKey, + GitHubEncryptedCredentialMaterial, + credentials + ); + +/** Decryption returns only durable installation state; access tokens are never persisted. */ +export const decryptGitHubCredentialMaterial = ( + encryptionKey: Redacted.Redacted, + encryptedCredential: EncryptedGitHubCredential +): Effect.Effect< + GitHubEncryptedCredentialMaterial, + IntegrationCredentialEncryptionError +> => + decryptIntegrationCredentialMaterial( + encryptionKey, + GitHubEncryptedCredentialMaterial, + encryptedCredential, + (decoded) => decoded + ); diff --git a/integrations/github/src/github-errors.ts b/integrations/github/src/github-errors.ts new file mode 100644 index 00000000..16c5670b --- /dev/null +++ b/integrations/github/src/github-errors.ts @@ -0,0 +1,32 @@ +/** biome-ignore-all lint/performance/noBarrelFile: provider error aliases are the public error surface */ +import * as Schema from "effect/Schema"; + +export { + IntegrationCredentialEncryptionError as GitHubCredentialEncryptionError, + IntegrationProviderAuthenticationError, + IntegrationProviderInvalidConfigurationError, + IntegrationProviderPermanentRejection, + IntegrationProviderRateLimitedError, + IntegrationProviderTemporaryFailure, + IntegrationRequestSignatureError as GitHubSignatureVerificationError, +} from "@feeblo/integration-core"; + +import type { + IntegrationProviderAuthenticationError, + IntegrationProviderInvalidConfigurationError, + IntegrationProviderPermanentRejection, + IntegrationProviderRateLimitedError, + IntegrationProviderTemporaryFailure, +} from "@feeblo/integration-core"; + +export class GitHubInboundPayloadError extends Schema.TaggedError()( + "GitHubInboundPayloadError", + { reason: Schema.String } +) {} + +export type GitHubApiFailure = + | IntegrationProviderAuthenticationError + | IntegrationProviderRateLimitedError + | IntegrationProviderInvalidConfigurationError + | IntegrationProviderTemporaryFailure + | IntegrationProviderPermanentRejection; diff --git a/integrations/github/src/github-external-resource.ts b/integrations/github/src/github-external-resource.ts new file mode 100644 index 00000000..2ad81768 --- /dev/null +++ b/integrations/github/src/github-external-resource.ts @@ -0,0 +1,43 @@ +import { + type IntegrationExternalResourceDraft, + IntegrationExternalResourceType, +} from "@feeblo/integration-core"; + +/** + * Provider-normalized GitHub issue identity shared by the delivery worker and + * the user-requested create/link paths. One mapping keeps the persisted + * `displayKey`, `safeMetadata`, and `title` identical no matter which path + * recorded the issue. + */ +export const makeGitHubIssueExternalResourceDraft = ({ + issueNumber, + postId, + repositoryName, + repositoryOwner, + remoteId, + remoteUrl, + state, + title, +}: { + readonly issueNumber: number; + readonly postId: IntegrationExternalResourceDraft["postId"]; + readonly repositoryName: string; + readonly repositoryOwner: string; + readonly remoteId: string; + readonly remoteUrl: URL; + readonly state: "open" | "closed"; + readonly title: string; +}): IntegrationExternalResourceDraft => ({ + displayKey: `${repositoryOwner}/${repositoryName}#${issueNumber}`, + postId, + remoteId, + stateKey: state, + remoteUrl, + resourceType: IntegrationExternalResourceType.make("issue"), + safeMetadata: { + issueNumber, + repositoryName, + repositoryOwner, + }, + title, +}); diff --git a/integrations/github/src/github-inbound-schema.ts b/integrations/github/src/github-inbound-schema.ts new file mode 100644 index 00000000..539926f2 --- /dev/null +++ b/integrations/github/src/github-inbound-schema.ts @@ -0,0 +1,110 @@ +import * as Schema from "effect/Schema"; + +/** GitHub App installation identifier carried by every app-scoped delivery. */ +export const GitHubInstallationId = Schema.Int.check(Schema.isGreaterThan(0)); +export type GitHubInstallationId = Schema.Schema.Type< + typeof GitHubInstallationId +>; + +/** Issue actions that can change Feeblo's linked-resource status. */ +export const GitHubWebhookIssueAction = Schema.Literals([ + "assigned", + "closed", + "deleted", + "demilestoned", + "edited", + "labeled", + "locked", + "milestoned", + "opened", + "pinned", + "reopened", + "transferred", + "typed", + "unassigned", + "unlabeled", + "unlocked", + "unpinned", + "untyped", +]); +export type GitHubWebhookIssueAction = Schema.Schema.Type< + typeof GitHubWebhookIssueAction +>; + +/** Safe subset of an Issues webhook delivered to a GitHub App. */ +export const GitHubIssueWebhookPayload = Schema.Struct({ + action: GitHubWebhookIssueAction, + installation: Schema.Struct({ id: GitHubInstallationId }), + issue: Schema.Struct({ + html_url: Schema.URLFromString, + id: Schema.Number, + node_id: Schema.String, + number: Schema.Int, + state: Schema.Literals(["open", "closed"]), + title: Schema.String, + }), + repository: Schema.Struct({ + full_name: Schema.NonEmptyString, + id: Schema.Number, + name: Schema.NonEmptyString, + owner: Schema.Struct({ login: Schema.NonEmptyString }), + }), + sender: Schema.Struct({ + id: Schema.Number, + login: Schema.NonEmptyString, + }), +}); +export type GitHubIssueWebhookPayload = Schema.Schema.Type< + typeof GitHubIssueWebhookPayload +>; + +/** GitHub App lifecycle actions Feeblo persists for an installation. */ +export const GitHubInstallationWebhookAction = Schema.Literals([ + "created", + "deleted", + "suspend", + "unsuspend", +]); +export type GitHubInstallationWebhookAction = Schema.Schema.Type< + typeof GitHubInstallationWebhookAction +>; + +/** Safe subset of an Installation webhook used to enable or disable a connection. */ +export const GitHubInstallationWebhookPayload = Schema.Struct({ + action: GitHubInstallationWebhookAction, + installation: Schema.Struct({ id: GitHubInstallationId }), +}); +export type GitHubInstallationWebhookPayload = Schema.Schema.Type< + typeof GitHubInstallationWebhookPayload +>; + +/** Repository-selection updates are acknowledged so GitHub can retry neither stale nor unsupported deliveries. */ +export const GitHubInstallationRepositoriesWebhookPayload = Schema.Struct({ + action: Schema.Literals(["added", "removed"]), + installation: Schema.Struct({ id: GitHubInstallationId }), +}); +export type GitHubInstallationRepositoriesWebhookPayload = Schema.Schema.Type< + typeof GitHubInstallationRepositoriesWebhookPayload +>; + +/** Verified and decoded inbound GitHub App delivery, keyed by GitHub's delivery ID. */ +export const ParsedGitHubInboundRequest = Schema.Union([ + Schema.Struct({ + deliveryId: Schema.NonEmptyString, + kind: Schema.Literal("issue"), + payload: GitHubIssueWebhookPayload, + }), + Schema.Struct({ + deliveryId: Schema.NonEmptyString, + kind: Schema.Literal("installation"), + payload: GitHubInstallationWebhookPayload, + }), + Schema.Struct({ + deliveryId: Schema.NonEmptyString, + kind: Schema.Literal("installation_repositories"), + payload: GitHubInstallationRepositoriesWebhookPayload, + }), +]); +export type ParsedGitHubInboundRequest = Schema.Schema.Type< + typeof ParsedGitHubInboundRequest +>; diff --git a/integrations/github/src/github-issue-body.test.ts b/integrations/github/src/github-issue-body.test.ts new file mode 100644 index 00000000..57322899 --- /dev/null +++ b/integrations/github/src/github-issue-body.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from "@effect/vitest"; +import { + GITHUB_ISSUE_BODY_CHARACTER_LIMIT, + renderGitHubIssueBody, +} from "./github-issue-body"; + +describe("renderGitHubIssueBody", () => { + it("uses the post description as the issue body", () => { + const body = renderGitHubIssueBody({ + description: "Dark mode hurts my eyes at night.", + }); + + expect(body).toBe("Dark mode hurts my eyes at night."); + }); + + it("falls back to a generic body when the description is empty", () => { + const body = renderGitHubIssueBody({ description: " " }); + + expect(body).toBe("This issue was created from Feeblo feedback."); + }); + + it("falls back to a generic body without a description", () => { + const body = renderGitHubIssueBody({}); + + expect(body).toBe("This issue was created from Feeblo feedback."); + }); + + it("falls back to a generic body when the description is null", () => { + const body = renderGitHubIssueBody({ description: null }); + + expect(body).toBe("This issue was created from Feeblo feedback."); + }); + + it("returns the description unchanged when it is exactly at the limit", () => { + const description = "a".repeat(GITHUB_ISSUE_BODY_CHARACTER_LIMIT); + + expect(renderGitHubIssueBody({ description })).toBe(description); + }); + + it("truncates an over-limit description and reserves room for the marker", () => { + const description = "a".repeat(GITHUB_ISSUE_BODY_CHARACTER_LIMIT + 100); + + const body = renderGitHubIssueBody({ description }); + + expect(body.length).toBe(GITHUB_ISSUE_BODY_CHARACTER_LIMIT); + expect(body.endsWith("…[Truncated — view the full post on Feeblo]")).toBe( + true + ); + }); + + it("includes the post link in the truncation marker without exceeding the limit", () => { + const description = "a".repeat(GITHUB_ISSUE_BODY_CHARACTER_LIMIT + 100); + const postUrl = "https://feeblo.example/org/post/slug"; + + const body = renderGitHubIssueBody({ description, postUrl }); + + expect(body.length).toBe(GITHUB_ISSUE_BODY_CHARACTER_LIMIT); + expect(body.endsWith(`…[View the full post on Feeblo](${postUrl})`)).toBe( + true + ); + }); +}); diff --git a/integrations/github/src/github-issue-body.ts b/integrations/github/src/github-issue-body.ts new file mode 100644 index 00000000..d88b26e7 --- /dev/null +++ b/integrations/github/src/github-issue-body.ts @@ -0,0 +1,39 @@ +/** GitHub rejects issue bodies longer than 65,536 characters. */ +export const GITHUB_ISSUE_BODY_CHARACTER_LIMIT = 65_536; + +const truncationMarker = (postUrl: string | undefined): string => + postUrl === undefined + ? "\n\n…[Truncated — view the full post on Feeblo]" + : `\n\n…[View the full post on Feeblo](${postUrl})`; + +/** Renders the issue body from the Feeblo post description; the Feeblo backlink lives in the bot comment, not the body. */ +export const renderGitHubIssueBody = ({ + description, + postUrl, +}: { + readonly description?: string | null; + readonly postUrl?: string; +}): string => { + const body = description?.trim(); + if (body === undefined || body.length === 0) { + return "This issue was created from Feeblo feedback."; + } + if (body.length <= GITHUB_ISSUE_BODY_CHARACTER_LIMIT) { + return body; + } + const marker = truncationMarker(postUrl); + const truncatedLength = GITHUB_ISSUE_BODY_CHARACTER_LIMIT - marker.length; + return `${body.slice(0, Math.max(0, truncatedLength))}${marker}`; +}; + +/** Renders the issue title from the Feeblo post title; empty titles fall back to a stable label. */ +export const renderGitHubIssueTitle = ({ + title, +}: { + readonly title?: string | null; +}): string => { + const trimmed = title?.trim(); + return trimmed !== undefined && trimmed.length > 0 + ? trimmed + : "Feeblo feedback"; +}; diff --git a/integrations/github/src/github-manifest.ts b/integrations/github/src/github-manifest.ts new file mode 100644 index 00000000..8bcd81a1 --- /dev/null +++ b/integrations/github/src/github-manifest.ts @@ -0,0 +1,59 @@ +/** Browser-safe GitHub capability metadata; this module imports no Node APIs. */ +import { + IntegrationCapabilityKey, + IntegrationProviderKey, + IntegrationProviderManifest, +} from "@feeblo/integration-core/contracts"; +import * as Schema from "effect/Schema"; + +export const githubProviderKey = IntegrationProviderKey.make("github"); +export const githubIssueCreateCapabilityKey = IntegrationCapabilityKey.make( + "github.issue.create" +); +export const githubIssueWebhookCapabilityKey = IntegrationCapabilityKey.make( + "github.issue.webhook" +); + +/** GitHub App permissions required by Feeblo's bot integration. */ +export const GITHUB_APP_PERMISSIONS = { + /** Feeblo creates issues, writes backlink comments, and observes issue state. */ + issues: "write", + /** Metadata read access is granted automatically to GitHub Apps. */ + metadata: "read", +} as const; + +export const GitHubConnectionConfiguration = Schema.Struct({}); + +/** A repository and optional Feeblo board selection for automatic issue creation. */ +export const GitHubIssueCreateRouteConfiguration = Schema.Struct({ + version: Schema.Literal(1), + repositoryOwner: Schema.NonEmptyString, + repositoryName: Schema.NonEmptyString, + boardId: Schema.optionalKey(Schema.NonEmptyString), +}); +export type GitHubIssueCreateRouteConfiguration = Schema.Schema.Type< + typeof GitHubIssueCreateRouteConfiguration +>; + +/** Inbound webhooks are configured at the connection level, never per route. */ +export const GitHubIssueWebhookRouteConfiguration = Schema.Struct({ + version: Schema.Literal(1), +}); + +export const githubProviderManifest = IntegrationProviderManifest.make({ + provider: githubProviderKey, + displayName: "GitHub", + connectionMode: "github_app", + capabilities: [ + { + key: githubIssueCreateCapabilityKey, + direction: "outbound", + configVersion: 1, + }, + { + key: githubIssueWebhookCapabilityKey, + direction: "inbound", + configVersion: 1, + }, + ], +}); diff --git a/integrations/github/src/github-provider-registration.test.ts b/integrations/github/src/github-provider-registration.test.ts new file mode 100644 index 00000000..2035eef9 --- /dev/null +++ b/integrations/github/src/github-provider-registration.test.ts @@ -0,0 +1,535 @@ +import { createHmac } from "node:crypto"; +import { describe, expect, it } from "@effect/vitest"; +import { + asLegid, + IntegrationConnectionId, + IntegrationDeliveryId, + IntegrationEventId, + IntegrationRouteId, + PostId, + WorkspaceId, +} from "@feeblo/id"; +import { + type IntegrationExternalResourceDraft, + type IntegrationProviderDeliveryInput, + IntegrationProviderInvalidConfigurationError, + IntegrationProviderTemporaryFailure, +} from "@feeblo/integration-core"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Redacted from "effect/Redacted"; +import * as Schema from "effect/Schema"; +import type { GitHubApiClient, GitHubIssue } from "./github-api"; +import { makeGitHubIssueExternalResourceDraft } from "./github-external-resource"; +import { ParsedGitHubInboundRequest } from "./github-inbound-schema"; +import { + githubIssueCreateCapabilityKey, + githubProviderKey, +} from "./github-manifest"; +import { + makeGitHubCredentialResolver, + makeGitHubProviderRegistration, +} from "./github-provider-registration"; + +const deliveryInput: IntegrationProviderDeliveryInput = { + connection: { + credentialGeneration: 1, + id: asLegid(IntegrationConnectionId)("conn_1"), + lifecycleStatus: "active", + name: "octocat", + organizationId: asLegid(WorkspaceId)("org_1"), + provider: githubProviderKey, + safeMetadata: {}, + }, + delivery: { + actionKey: "github.issue.create:route_1", + attemptCount: 0, + eventId: asLegid(IntegrationEventId)("event_1"), + id: asLegid(IntegrationDeliveryId)("delivery_1"), + leaseExpiresAt: null, + leaseOwner: null, + nextAttemptAt: DateTime.makeUnsafe(new Date()), + orderingKey: null, + routeId: asLegid(IntegrationRouteId)("route_1"), + state: "pending", + }, + event: { + causalHopCount: 0, + correlationId: "corr_1", + data: { + actor: { kind: "end_user" }, + board: { id: "brd_1", name: "Ideas", slug: "ideas" }, + post: { + id: "pst_1", + description: "Dark mode hurts my eyes at night.", + status: { id: "pss_1", type: "PENDING" }, + title: "Dark mode", + url: "https://feeblo.example/org/post/ideas/dark-mode", + }, + }, + id: asLegid(IntegrationEventId)("event_1"), + occurredAt: DateTime.makeUnsafe(new Date()), + organizationId: asLegid(WorkspaceId)("org_1"), + origin: { kind: "feeblo" }, + type: "feedback.post.created", + version: 1, + }, + route: { + capabilityKey: githubIssueCreateCapabilityKey, + configVersion: 1, + connectionId: asLegid(IntegrationConnectionId)("conn_1"), + enabled: true, + eventTypes: ["feedback.post.created"], + id: asLegid(IntegrationRouteId)("route_1"), + provider: githubProviderKey, + providerConfig: { + version: 1, + repositoryOwner: "acme", + repositoryName: "feedback", + }, + safeMetadata: {}, + }, +}; + +const apiClient: GitHubApiClient = { + createIssueBacklinkComment: () => Effect.die("not used"), + createIssue: () => + Effect.succeed({ + html_url: new URL("https://github.com/acme/feedback/issues/7"), + id: 7, + node_id: "I_7", + number: 7, + state: "open", + title: "Dark mode", + }), + createInstallationAccessToken: () => Effect.die("not used"), + deleteInstallation: () => Effect.die("not used"), + exchangeUserAccessToken: () => Effect.die("not used"), + getIssue: () => Effect.die("not used"), + listInstallationRepositories: () => Effect.die("not used"), + listUserInstallations: () => Effect.die("not used"), +}; + +describe("GitHub provider registration", () => { + it.effect( + "returns a decoded issue webhook value that is valid at the server boundary", + () => + Effect.gen(function* () { + const secret = Redacted.make("webhook-secret"); + const registration = makeGitHubProviderRegistration({ + apiClient, + credentialResolver: { + loadGitHubCredentials: () => + Effect.succeed({ accessToken: Redacted.make("token") }), + }, + webhookSecret: secret, + }); + const handler = registration.inboundHandlers[0]; + if (handler === undefined) { + return; + } + const rawBody = + '{"action":"closed","installation":{"id":42},"issue":{"html_url":"https://github.com/acme/feedback/issues/7","id":7,"node_id":"I_7","number":7,"state":"closed","title":"Dark mode"},"repository":{"full_name":"acme/feedback","id":1,"name":"feedback","owner":{"login":"acme"}},"sender":{"id":2,"login":"octocat"}}'; + const signature = `sha256=${createHmac("sha256", Redacted.value(secret)).update(rawBody).digest("hex")}`; + const response = yield* handler.handle({ + headers: { + "x-github-delivery": "delivery_issue_7", + "x-github-event": "issues", + "x-hub-signature-256": signature, + }, + rawBody, + }); + + expect(response.status).toBe(200); + const parsed = yield* Schema.decodeUnknownEffect( + Schema.toType(ParsedGitHubInboundRequest) + )(response.body); + expect(parsed.kind).toBe("issue"); + }) + ); + + it.effect( + "creates an issue from the post description and comments the Feeblo backlink", + () => + Effect.gen(function* () { + let issueBody = ""; + let commentedBacklinkUrl: URL | undefined; + let externalResourceDrafts: + | readonly IntegrationExternalResourceDraft[] + | undefined; + const registration = makeGitHubProviderRegistration({ + apiClient: { + ...apiClient, + createIssue: (input) => { + issueBody = input.body; + return apiClient.createIssue(input); + }, + createIssueBacklinkComment: (input) => { + commentedBacklinkUrl = input.backlinkUrl; + return Effect.void; + }, + }, + credentialResolver: { + loadGitHubCredentials: () => + Effect.succeed({ accessToken: Redacted.make("token") }), + }, + webhookSecret: Redacted.make("webhook-secret"), + }); + const handler = registration.handlers[0]; + if (handler === undefined) { + return; + } + const result = yield* Effect.exit(handler.deliver(deliveryInput)); + expect(Exit.isSuccess(result)).toBe(true); + if (Exit.isSuccess(result)) { + externalResourceDrafts = result.value.externalResourceDrafts; + } + expect(issueBody).toBe("Dark mode hurts my eyes at night."); + expect(commentedBacklinkUrl?.href).toBe( + "https://feeblo.example/org/post/ideas/dark-mode" + ); + expect(externalResourceDrafts?.[0]?.remoteId).toBe("I_7"); + expect(externalResourceDrafts?.[0]?.stateKey).toBe("open"); + expect(externalResourceDrafts?.[0]?.title).toBe("Dark mode"); + expect(externalResourceDrafts?.[0]?.remoteUrl.href).toBe( + "https://github.com/acme/feedback/issues/7" + ); + }) + ); + + it.effect( + "accepts a globally delivered signed installation-created webhook", + () => + Effect.gen(function* () { + const secret = Redacted.make("webhook-secret"); + const registration = makeGitHubProviderRegistration({ + apiClient, + credentialResolver: { + loadGitHubCredentials: () => + Effect.succeed({ accessToken: Redacted.make("token") }), + }, + webhookSecret: secret, + }); + const handler = registration.inboundHandlers[0]; + if (handler === undefined) { + return; + } + const rawBody = '{"action":"created","installation":{"id":42}}'; + const signature = `sha256=${createHmac("sha256", Redacted.value(secret)).update(rawBody).digest("hex")}`; + const response = yield* handler.handle({ + headers: { + "x-github-delivery": "delivery_42", + "x-github-event": "installation", + "x-hub-signature-256": signature, + }, + rawBody, + }); + expect(response.status).toBe(200); + const parsed = yield* Schema.decodeUnknownEffect( + ParsedGitHubInboundRequest + )(response.body); + expect(parsed.kind).toBe("installation"); + if (parsed.kind === "installation") { + expect(parsed.payload.installation.id).toBe(42); + } + }) + ); +}); + +describe("makeGitHubIssueExternalResourceDraft", () => { + it("normalizes a GitHub issue into a provider-neutral resource", () => { + const issue: GitHubIssue = { + html_url: new URL("https://github.com/acme/feedback/issues/7"), + id: 7, + node_id: "I_7", + number: 7, + state: "open", + title: "Dark mode", + }; + const draft = makeGitHubIssueExternalResourceDraft({ + issueNumber: issue.number, + postId: asLegid(PostId)("pst_1"), + repositoryName: "feedback", + repositoryOwner: "acme", + remoteId: issue.node_id, + remoteUrl: issue.html_url, + state: issue.state, + title: issue.title, + }); + + expect(draft.displayKey).toBe("acme/feedback#7"); + expect(draft.remoteId).toBe("I_7"); + expect(draft.stateKey).toBe("open"); + expect(draft.remoteUrl.href).toBe( + "https://github.com/acme/feedback/issues/7" + ); + expect(draft.resourceType).toBe("issue"); + expect(draft.safeMetadata).toEqual({ + issueNumber: 7, + repositoryName: "feedback", + repositoryOwner: "acme", + }); + expect(draft.title).toBe("Dark mode"); + }); +}); + +describe("GitHub provider credential resolver", () => { + const installationTokenResolver = { + getInstallationAccessToken: ({ + installationId, + }: { + readonly installationId: string; + }) => Effect.succeed(Redacted.make(`token_${installationId}`)), + }; + + it.effect("mints credentials for an installed connection", () => + Effect.gen(function* () { + const resolver = makeGitHubCredentialResolver({ + installationTokenResolver, + loadInstallationId: () => Effect.succeed("12345"), + }); + + const credentials = yield* resolver.loadGitHubCredentials(deliveryInput); + + expect(Redacted.value(credentials.accessToken)).toBe("token_12345"); + }) + ); + + it.effect("rejects a connection without a GitHub installation", () => + Effect.gen(function* () { + const resolver = makeGitHubCredentialResolver({ + installationTokenResolver, + loadInstallationId: () => Effect.succeed(null), + }); + + const tag = yield* resolver.loadGitHubCredentials(deliveryInput).pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(tag).toBe("IntegrationProviderInvalidConfigurationError"); + }) + ); + + it.effect("maps token minting failures to a temporary provider failure", () => + Effect.gen(function* () { + const resolver = makeGitHubCredentialResolver({ + installationTokenResolver: { + getInstallationAccessToken: () => + Effect.fail( + new IntegrationProviderTemporaryFailure({ + message: "mint failed", + provider: githubProviderKey, + }) + ), + }, + loadInstallationId: () => Effect.succeed("12345"), + }); + + const tag = yield* resolver.loadGitHubCredentials(deliveryInput).pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(tag).toBe("IntegrationProviderTemporaryFailure"); + }) + ); + + it.effect("retains invalid configuration failures from token minting", () => + Effect.gen(function* () { + const resolver = makeGitHubCredentialResolver({ + installationTokenResolver: { + getInstallationAccessToken: () => + Effect.fail( + new IntegrationProviderInvalidConfigurationError({ + message: "mint configuration invalid", + provider: githubProviderKey, + }) + ), + }, + loadInstallationId: () => Effect.succeed("12345"), + }); + + const tag = yield* resolver.loadGitHubCredentials(deliveryInput).pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(tag).toBe("IntegrationProviderInvalidConfigurationError"); + }) + ); +}); + +describe("GitHub App webhook handler", () => { + const webhookSecret = Redacted.make("webhook-secret"); + const signatureFor = (rawBody: string) => + `sha256=${createHmac("sha256", Redacted.value(webhookSecret)) + .update(rawBody) + .digest("hex")}`; + + const makeHandler = () => { + const registration = makeGitHubProviderRegistration({ + apiClient, + credentialResolver: { + loadGitHubCredentials: () => + Effect.succeed({ accessToken: Redacted.make("token") }), + }, + webhookSecret, + }); + return registration.inboundHandlers[0]; + }; + + it.effect("rejects a delivery with an invalid signature", () => + Effect.gen(function* () { + const handler = makeHandler(); + if (handler === undefined) { + return; + } + const response = yield* handler.handle({ + headers: { + "x-github-delivery": "delivery_1", + "x-github-event": "issues", + "x-hub-signature-256": "sha256=deadbeef", + }, + rawBody: '{"action":"opened"}', + }); + + expect(response.status).toBe(401); + expect(response.body).toBe("invalid request signature"); + }) + ); + + it.effect("acknowledges an unsupported GitHub event without retrying", () => + Effect.gen(function* () { + const handler = makeHandler(); + if (handler === undefined) { + return; + } + const rawBody = '{"ref":"refs/heads/main"}'; + const response = yield* handler.handle({ + headers: { + "x-github-delivery": "delivery_push_1", + "x-github-event": "push", + "x-hub-signature-256": signatureFor(rawBody), + }, + rawBody, + }); + + expect(response.status).toBe(202); + expect(response.body).toBe("unsupported GitHub webhook event"); + }) + ); + + it.effect("rejects a malformed issue payload", () => + Effect.gen(function* () { + const handler = makeHandler(); + if (handler === undefined) { + return; + } + const rawBody = '{"action":"opened"}'; + const response = yield* handler.handle({ + headers: { + "x-github-delivery": "delivery_bad_1", + "x-github-event": "issues", + "x-hub-signature-256": signatureFor(rawBody), + }, + rawBody, + }); + + expect(response.status).toBe(400); + expect(response.body).toBe("invalid request payload"); + }) + ); + + it.effect("rejects a signed delivery that is missing its delivery id", () => + Effect.gen(function* () { + const handler = makeHandler(); + if (handler === undefined) { + return; + } + const rawBody = '{"action":"opened"}'; + const response = yield* handler.handle({ + headers: { + "x-github-event": "issues", + "x-hub-signature-256": signatureFor(rawBody), + }, + rawBody, + }); + + expect(response.status).toBe(400); + expect(response.body).toBe("invalid request payload"); + }) + ); +}); + +describe("GitHub issue-create handler", () => { + const makeHandler = () => { + const registration = makeGitHubProviderRegistration({ + apiClient, + credentialResolver: { + loadGitHubCredentials: () => + Effect.succeed({ accessToken: Redacted.make("token") }), + }, + webhookSecret: Redacted.make("webhook-secret"), + }); + return registration.handlers[0]; + }; + + it.effect("rejects events that are not new posts", () => + Effect.gen(function* () { + const handler = makeHandler(); + if (handler === undefined) { + return; + } + const tag = yield* handler + .deliver({ + ...deliveryInput, + event: { + ...deliveryInput.event, + type: "feedback.post.status_changed", + }, + }) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(tag).toBe("IntegrationProviderInvalidConfigurationError"); + }) + ); + + it.effect( + "skips creation when the route board does not match the post board", + () => + Effect.gen(function* () { + const handler = makeHandler(); + if (handler === undefined) { + return; + } + const result = yield* handler.deliver({ + ...deliveryInput, + route: { + ...deliveryInput.route, + providerConfig: { + version: 1, + repositoryOwner: "acme", + repositoryName: "feedback", + boardId: "brd_other", + }, + }, + }); + + expect(result.externalResourceDrafts).toBeUndefined(); + expect(result.httpStatus).toBeUndefined(); + }) + ); +}); diff --git a/integrations/github/src/github-provider-registration.ts b/integrations/github/src/github-provider-registration.ts new file mode 100644 index 00000000..d3a879fb --- /dev/null +++ b/integrations/github/src/github-provider-registration.ts @@ -0,0 +1,307 @@ +import { + type IntegrationInboundCapabilityHandler, + type IntegrationInboundRequest, + type IntegrationInboundResponse, + IntegrationPostEventData, + type IntegrationProviderDeliveryInput, + IntegrationProviderInvalidConfigurationError, + type IntegrationProviderRegistration, + IntegrationProviderTemporaryFailure, +} from "@feeblo/integration-core"; +import * as Effect from "effect/Effect"; +import type * as Redacted from "effect/Redacted"; +import * as Result from "effect/Result"; +import * as Schema from "effect/Schema"; +import { type GitHubApiClient, makeGitHubApiClient } from "./github-api"; +import type { GitHubInstallationTokenResolver } from "./github-app-auth"; +import { GitHubInboundPayloadError } from "./github-errors"; +import { makeGitHubIssueExternalResourceDraft } from "./github-external-resource"; +import { + GitHubInstallationRepositoriesWebhookPayload, + GitHubInstallationWebhookPayload, + GitHubIssueWebhookPayload, + type ParsedGitHubInboundRequest, +} from "./github-inbound-schema"; +import { + renderGitHubIssueBody, + renderGitHubIssueTitle, +} from "./github-issue-body"; +import { + GitHubConnectionConfiguration, + GitHubIssueCreateRouteConfiguration, + GitHubIssueWebhookRouteConfiguration, + githubIssueCreateCapabilityKey, + githubIssueWebhookCapabilityKey, + githubProviderKey, + githubProviderManifest, +} from "./github-manifest"; +import { verifyGitHubWebhookSignature } from "./github-signature"; + +/** Per-connection GitHub App token resolver; durable ciphertext contains only installation identity. */ +export interface GitHubProviderCredentialResolver { + readonly loadGitHubCredentials: ( + input: IntegrationProviderDeliveryInput + ) => Effect.Effect< + { readonly accessToken: Redacted.Redacted }, + | IntegrationProviderInvalidConfigurationError + | IntegrationProviderTemporaryFailure + >; +} + +export const makeGitHubCredentialResolver = ({ + installationTokenResolver, + loadInstallationId, +}: { + readonly installationTokenResolver: GitHubInstallationTokenResolver; + readonly loadInstallationId: ( + input: IntegrationProviderDeliveryInput + ) => Effect.Effect; +}): GitHubProviderCredentialResolver => ({ + loadGitHubCredentials: (input) => + Effect.gen(function* () { + const installationId = yield* loadInstallationId(input); + if (installationId === null) { + return yield* new IntegrationProviderInvalidConfigurationError({ + message: "GitHub credentials are unavailable", + provider: githubProviderKey, + }); + } + const accessToken = yield* installationTokenResolver + .getInstallationAccessToken({ + installationId, + }) + .pipe( + Effect.mapError((error) => + Schema.is(IntegrationProviderInvalidConfigurationError)(error) + ? error + : new IntegrationProviderTemporaryFailure({ + message: "GitHub installation token could not be minted", + provider: githubProviderKey, + }) + ) + ); + return { accessToken }; + }), +}); + +const parseGitHubAppWebhook = ({ + deliveryId, + eventName, + rawBody, +}: { + readonly deliveryId: string | undefined; + readonly eventName: string | undefined; + readonly rawBody: string; +}): Effect.Effect => { + if (deliveryId === undefined || deliveryId.length === 0) { + return Effect.fail( + new GitHubInboundPayloadError({ + reason: "GitHub webhook delivery id is missing", + }) + ); + } + switch (eventName) { + case "issues": + return Schema.decodeUnknownEffect( + Schema.fromJsonString(GitHubIssueWebhookPayload) + )(rawBody).pipe( + Effect.map((payload) => ({ + deliveryId, + kind: "issue" as const, + payload, + })), + Effect.mapError( + () => + new GitHubInboundPayloadError({ + reason: "GitHub App webhook payload is invalid", + }) + ) + ); + case "installation": + return Schema.decodeUnknownEffect( + Schema.fromJsonString(GitHubInstallationWebhookPayload) + )(rawBody).pipe( + Effect.map((payload) => ({ + deliveryId, + kind: "installation" as const, + payload, + })), + Effect.mapError( + () => + new GitHubInboundPayloadError({ + reason: "GitHub App webhook payload is invalid", + }) + ) + ); + case "installation_repositories": + return Schema.decodeUnknownEffect( + Schema.fromJsonString(GitHubInstallationRepositoriesWebhookPayload) + )(rawBody).pipe( + Effect.map((payload) => ({ + deliveryId, + kind: "installation_repositories" as const, + payload, + })), + Effect.mapError( + () => + new GitHubInboundPayloadError({ + reason: "GitHub App webhook payload is invalid", + }) + ) + ); + default: + return Effect.fail( + new GitHubInboundPayloadError({ + reason: "GitHub App webhook event is unsupported", + }) + ); + } +}; + +/** Provider-owned raw GitHub App webhook authentication and decoding, before any domain service runs. */ +const makeGitHubAppWebhookHandler = ({ + webhookSecret, +}: { + readonly webhookSecret: Redacted.Redacted; +}): IntegrationInboundCapabilityHandler => ({ + capabilityKey: githubIssueWebhookCapabilityKey, + handle: (input: IntegrationInboundRequest) => + Effect.gen(function* () { + const verified = yield* Effect.result( + verifyGitHubWebhookSignature({ + rawBody: input.rawBody, + signatureHeader: input.headers["x-hub-signature-256"], + webhookSecret, + }) + ); + if (Result.isFailure(verified)) { + return { + body: "invalid request signature", + status: 401, + } satisfies IntegrationInboundResponse; + } + const parsed = yield* Effect.result( + parseGitHubAppWebhook({ + deliveryId: input.headers["x-github-delivery"], + eventName: input.headers["x-github-event"], + rawBody: input.rawBody, + }) + ); + if (Result.isFailure(parsed)) { + return parsed.failure.reason === + "GitHub App webhook event is unsupported" + ? ({ + body: "unsupported GitHub webhook event", + status: 202, + } satisfies IntegrationInboundResponse) + : ({ + body: "invalid request payload", + status: 400, + } satisfies IntegrationInboundResponse); + } + return { + body: parsed.success, + status: 200, + } satisfies IntegrationInboundResponse; + }), +}); + +/** GitHub has one outbound issue-create capability and one separate inbound webhook capability. */ +export const makeGitHubProviderRegistration = ({ + apiClient = makeGitHubApiClient(), + credentialResolver, + webhookSecret, +}: { + readonly apiClient?: GitHubApiClient; + readonly credentialResolver: GitHubProviderCredentialResolver; + readonly webhookSecret: Redacted.Redacted; +}): IntegrationProviderRegistration => { + const issueCreateHandler = { + capabilityKey: githubIssueCreateCapabilityKey, + deliver: (input: IntegrationProviderDeliveryInput) => + Effect.gen(function* () { + if (input.event.type !== "feedback.post.created") { + return yield* new IntegrationProviderInvalidConfigurationError({ + message: "GitHub issue creation only supports new posts", + provider: githubProviderKey, + }); + } + const routeConfig = yield* Schema.decodeUnknownEffect( + GitHubIssueCreateRouteConfiguration + )(input.route.providerConfig).pipe( + Effect.mapError( + () => + new IntegrationProviderInvalidConfigurationError({ + message: "GitHub issue route configuration is invalid", + provider: githubProviderKey, + }) + ) + ); + const eventData = yield* Schema.decodeUnknownEffect( + IntegrationPostEventData + )(input.event.data).pipe( + Effect.mapError( + () => + new IntegrationProviderInvalidConfigurationError({ + message: "GitHub event payload is invalid", + provider: githubProviderKey, + }) + ) + ); + // The route's board selection is intentionally checked by the provider + // rather than broadening the kernel event selection model. + if ( + routeConfig.boardId !== undefined && + routeConfig.boardId !== eventData.board.id + ) { + return {}; + } + const credentials = + yield* credentialResolver.loadGitHubCredentials(input); + const issue = yield* apiClient.createIssue({ + accessToken: credentials.accessToken, + body: renderGitHubIssueBody({ + description: eventData.post.description ?? null, + postUrl: eventData.post.url.toString(), + }), + repositoryName: routeConfig.repositoryName, + repositoryOwner: routeConfig.repositoryOwner, + title: renderGitHubIssueTitle({ title: eventData.post.title }), + }); + // The bot backlink comment is part of the delivery so a retry after a + // crash before acknowledgement re-runs both calls (at-least-once). + yield* apiClient.createIssueBacklinkComment({ + accessToken: credentials.accessToken, + backlinkUrl: eventData.post.url, + issueNumber: issue.number, + repositoryName: routeConfig.repositoryName, + repositoryOwner: routeConfig.repositoryOwner, + }); + return { + externalResourceDrafts: [ + makeGitHubIssueExternalResourceDraft({ + issueNumber: issue.number, + postId: eventData.post.id, + repositoryName: routeConfig.repositoryName, + repositoryOwner: routeConfig.repositoryOwner, + remoteId: issue.node_id, + remoteUrl: issue.html_url, + state: issue.state, + title: issue.title, + }), + ], + httpStatus: 201, + }; + }), + }; + return { + connectionConfigurationSchema: GitHubConnectionConfiguration, + handlers: [issueCreateHandler], + inboundHandlers: [makeGitHubAppWebhookHandler({ webhookSecret })], + manifest: githubProviderManifest, + routeConfigurationSchemas: new Map([ + [githubIssueCreateCapabilityKey, GitHubIssueCreateRouteConfiguration], + [githubIssueWebhookCapabilityKey, GitHubIssueWebhookRouteConfiguration], + ]), + }; +}; diff --git a/integrations/github/src/github-signature.test.ts b/integrations/github/src/github-signature.test.ts new file mode 100644 index 00000000..901018ef --- /dev/null +++ b/integrations/github/src/github-signature.test.ts @@ -0,0 +1,38 @@ +import { createHmac } from "node:crypto"; +import { describe, expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Redacted from "effect/Redacted"; +import { verifyGitHubWebhookSignature } from "./github-signature"; + +const webhookSecret = Redacted.make("github-webhook-secret"); +const signatureFor = (rawBody: string) => + `sha256=${createHmac("sha256", Redacted.value(webhookSecret)) + .update(rawBody) + .digest("hex")}`; + +describe("verifyGitHubWebhookSignature", () => { + it.effect("accepts an HMAC SHA-256 signature over the raw body", () => + Effect.gen(function* () { + const rawBody = '{"action":"closed"}'; + yield* verifyGitHubWebhookSignature({ + rawBody, + signatureHeader: signatureFor(rawBody), + webhookSecret, + }); + }) + ); + + it.effect("rejects a tampered request", () => + Effect.gen(function* () { + const result = yield* Effect.exit( + verifyGitHubWebhookSignature({ + rawBody: "tampered", + signatureHeader: signatureFor("original"), + webhookSecret, + }) + ); + expect(Exit.isFailure(result)).toBe(true); + }) + ); +}); diff --git a/integrations/github/src/github-signature.ts b/integrations/github/src/github-signature.ts new file mode 100644 index 00000000..064870d6 --- /dev/null +++ b/integrations/github/src/github-signature.ts @@ -0,0 +1,38 @@ +import { createHmac, timingSafeEqual } from "node:crypto"; +import { IntegrationRequestSignatureError } from "@feeblo/integration-core"; +import * as Effect from "effect/Effect"; +import * as Redacted from "effect/Redacted"; +import type { GitHubSignatureVerificationError } from "./github-errors"; + +/** Verifies GitHub's X-Hub-Signature-256 against the exact raw UTF-8 body. */ +export const verifyGitHubWebhookSignature = ({ + rawBody, + signatureHeader, + webhookSecret, +}: { + readonly rawBody: string; + readonly signatureHeader: string | undefined; + readonly webhookSecret: Redacted.Redacted; +}): Effect.Effect => { + if (signatureHeader === undefined || !signatureHeader.startsWith("sha256=")) { + return Effect.fail( + new IntegrationRequestSignatureError({ + reason: "GitHub webhook signature is missing or invalid", + }) + ); + } + const expected = createHmac("sha256", Redacted.value(webhookSecret)) + .update(rawBody) + .digest("hex"); + const received = signatureHeader.slice("sha256=".length); + const expectedBuffer = Buffer.from(expected, "hex"); + const receivedBuffer = Buffer.from(received, "hex"); + return expectedBuffer.length === receivedBuffer.length && + timingSafeEqual(expectedBuffer, receivedBuffer) + ? Effect.void + : Effect.fail( + new IntegrationRequestSignatureError({ + reason: "GitHub webhook signature does not match", + }) + ); +}; diff --git a/integrations/github/src/index.ts b/integrations/github/src/index.ts new file mode 100644 index 00000000..162e2ca7 --- /dev/null +++ b/integrations/github/src/index.ts @@ -0,0 +1,12 @@ +/** biome-ignore-all lint/performance/noBarrelFile: package public entry point */ +/** Public GitHub provider package entry point. */ +export * from "./github-api"; +export * from "./github-app-auth"; +export * from "./github-credentials"; +export * from "./github-errors"; +export * from "./github-external-resource"; +export * from "./github-inbound-schema"; +export * from "./github-issue-body"; +export * from "./github-manifest"; +export * from "./github-provider-registration"; +export * from "./github-signature"; diff --git a/integrations/github/tsconfig.json b/integrations/github/tsconfig.json new file mode 100644 index 00000000..e47adcd4 --- /dev/null +++ b/integrations/github/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@feeblo/config/tsconfig.base.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "composite": true + }, + "include": ["src/**/*"] +} diff --git a/integrations/github/vitest.config.ts b/integrations/github/vitest.config.ts new file mode 100644 index 00000000..95f9062d --- /dev/null +++ b/integrations/github/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { environment: "node", include: ["src/**/*.test.ts"], pool: "threads" }, +}); diff --git a/integrations/slack/package.json b/integrations/slack/package.json index fa7b30ff..7c4d5761 100644 --- a/integrations/slack/package.json +++ b/integrations/slack/package.json @@ -22,7 +22,7 @@ "effect": "catalog:" }, "devDependencies": { - "@effect/vitest": "4.0.0-beta.94", + "@effect/vitest": "catalog:", "@feeblo/config": "workspace:*", "@feeblo/id": "workspace:*", "@types/node": "catalog:", diff --git a/integrations/slack/src/slack-errors.ts b/integrations/slack/src/slack-errors.ts index 9bbaf307..4a7cba27 100644 --- a/integrations/slack/src/slack-errors.ts +++ b/integrations/slack/src/slack-errors.ts @@ -20,7 +20,7 @@ import type { } from "@feeblo/integration-core"; /** Failure parsing a verified Slack inbound request body into its typed payload. */ -export class SlackInboundPayloadError extends Schema.TaggedErrorClass()( +export class SlackInboundPayloadError extends Schema.TaggedError()( "SlackInboundPayloadError", { reason: Schema.String } ) {} diff --git a/integrations/slack/src/slack-manifest.ts b/integrations/slack/src/slack-manifest.ts index 66d44c02..ae9a9258 100644 --- a/integrations/slack/src/slack-manifest.ts +++ b/integrations/slack/src/slack-manifest.ts @@ -8,6 +8,12 @@ import * as Schema from "effect/Schema"; /** Provider key owned by the Slack adapter, outside the provider-neutral kernel. */ export const slackProviderKey = IntegrationProviderKey.make("slack"); +export const slackChannelNotificationsCapabilityKey = + IntegrationCapabilityKey.make("channel.notifications"); +export const slackCommandsCapabilityKey = + IntegrationCapabilityKey.make("commands"); +export const slackMessageActionCapabilityKey = + IntegrationCapabilityKey.make("message.action"); /** Slack OAuth scopes requested during workspace installation. */ export const SLACK_OAUTH_SCOPES = [ @@ -54,17 +60,17 @@ export const slackProviderManifest = IntegrationProviderManifest.make({ connectionMode: "oauth2", capabilities: [ { - key: IntegrationCapabilityKey.make("channel.notifications"), + key: slackChannelNotificationsCapabilityKey, direction: "outbound", configVersion: 1, }, { - key: IntegrationCapabilityKey.make("commands"), + key: slackCommandsCapabilityKey, direction: "inbound", configVersion: 1, }, { - key: IntegrationCapabilityKey.make("message.action"), + key: slackMessageActionCapabilityKey, direction: "inbound", configVersion: 1, }, diff --git a/integrations/slack/src/slack-provider-registration.test.ts b/integrations/slack/src/slack-provider-registration.test.ts index 04dc6be2..4dd23b4b 100644 --- a/integrations/slack/src/slack-provider-registration.test.ts +++ b/integrations/slack/src/slack-provider-registration.test.ts @@ -17,7 +17,7 @@ import * as Exit from "effect/Exit"; import * as Redacted from "effect/Redacted"; import { describe, expect, it } from "vitest"; import type { SlackApiClient } from "./slack-api"; -import { slackProviderKey } from "./slack-manifest"; +import { slackChannelNotificationsCapabilityKey, slackProviderKey } from "./slack-manifest"; import { makeSlackProviderRegistration } from "./slack-provider-registration"; const signingSecret = Redacted.make("signing-secret"); @@ -68,7 +68,7 @@ const deliveryInput = ( version: 1, }, route: { - capabilityKey: "channel.notifications", + capabilityKey: slackChannelNotificationsCapabilityKey, configVersion: 1, connectionId: asLegid(IntegrationConnectionId)("conn_1"), enabled: true, diff --git a/integrations/slack/src/slack-provider-registration.ts b/integrations/slack/src/slack-provider-registration.ts index 1fc275ce..4aac29e4 100644 --- a/integrations/slack/src/slack-provider-registration.ts +++ b/integrations/slack/src/slack-provider-registration.ts @@ -28,6 +28,9 @@ import { import { SlackChannelNotificationRouteConfiguration, SlackInboundRouteConfiguration, + slackChannelNotificationsCapabilityKey, + slackCommandsCapabilityKey, + slackMessageActionCapabilityKey, slackProviderKey, slackProviderManifest, } from "./slack-manifest"; @@ -108,7 +111,7 @@ const makeSlackInboundHandler = ({ parse, signingSecret, }: { - readonly capabilityKey: "commands" | "message.action"; + readonly capabilityKey: IntegrationInboundCapabilityHandler["capabilityKey"]; readonly parse: ( rawBody: string ) => Effect.Effect; @@ -211,7 +214,7 @@ export const makeSlackProviderRegistration = ({ readonly signingSecret: Redacted.Redacted; }): IntegrationProviderRegistration => { const channelNotificationsHandler = { - capabilityKey: "channel.notifications" as const, + capabilityKey: slackChannelNotificationsCapabilityKey, deliver: (input: IntegrationProviderDeliveryInput) => Effect.gen(function* () { if (input.event.type !== "feedback.post.created") { @@ -288,21 +291,21 @@ export const makeSlackProviderRegistration = ({ handlers: [channelNotificationsHandler], inboundHandlers: [ makeSlackInboundHandler({ - capabilityKey: "commands", + capabilityKey: slackCommandsCapabilityKey, parse: parseSlashCommand, signingSecret, }), makeSlackInboundHandler({ - capabilityKey: "message.action", + capabilityKey: slackMessageActionCapabilityKey, parse: parseInteractive, signingSecret, }), ], manifest: slackProviderManifest, routeConfigurationSchemas: new Map([ - ["channel.notifications", SlackChannelNotificationRouteConfiguration], - ["commands", SlackInboundRouteConfiguration], - ["message.action", SlackInboundRouteConfiguration], + [slackChannelNotificationsCapabilityKey, SlackChannelNotificationRouteConfiguration], + [slackCommandsCapabilityKey, SlackInboundRouteConfiguration], + [slackMessageActionCapabilityKey, SlackInboundRouteConfiguration], ]), }; }; diff --git a/integrations/webhook/package.json b/integrations/webhook/package.json index 30c8bf2b..94807edd 100644 --- a/integrations/webhook/package.json +++ b/integrations/webhook/package.json @@ -19,7 +19,7 @@ "standardwebhooks": "1.0.0" }, "devDependencies": { - "@effect/vitest": "4.0.0-beta.94", + "@effect/vitest": "catalog:", "@feeblo/config": "workspace:*", "@feeblo/id": "workspace:*", "@types/node": "catalog:", diff --git a/integrations/webhook/src/webhook-errors.ts b/integrations/webhook/src/webhook-errors.ts index 44f4f079..499d7993 100644 --- a/integrations/webhook/src/webhook-errors.ts +++ b/integrations/webhook/src/webhook-errors.ts @@ -1,19 +1,19 @@ import * as Schema from "effect/Schema"; /** Typed failure produced when a webhook endpoint violates outbound security policy. */ -export class WebhookEndpointSecurityError extends Schema.TaggedErrorClass()( +export class WebhookEndpointSecurityError extends Schema.TaggedError()( "WebhookEndpointSecurityError", { reason: Schema.String } ) {} /** Typed failure produced when encrypted webhook credentials cannot be processed. */ -export class WebhookCredentialEncryptionError extends Schema.TaggedErrorClass()( +export class WebhookCredentialEncryptionError extends Schema.TaggedError()( "WebhookCredentialEncryptionError", { operation: Schema.Literals(["encrypt", "decrypt"]), reason: Schema.String } ) {} /** Typed failure produced when signing-key generation or Standard Webhooks signing fails. */ -export class WebhookSigningError extends Schema.TaggedErrorClass()( +export class WebhookSigningError extends Schema.TaggedError()( "WebhookSigningError", { operation: Schema.Literals(["generate", "sign"]) } ) {} @@ -23,7 +23,7 @@ export class WebhookSigningError extends Schema.TaggedErrorClass()( +export class WebhookTransportError extends Schema.TaggedError()( "WebhookTransportError", { kind: Schema.Literals(["timeout", "network", "payload_too_large"]), diff --git a/integrations/webhook/src/webhook-manifest.ts b/integrations/webhook/src/webhook-manifest.ts index 6902b1a1..2c1360d4 100644 --- a/integrations/webhook/src/webhook-manifest.ts +++ b/integrations/webhook/src/webhook-manifest.ts @@ -9,6 +9,8 @@ import * as Schema from "effect/Schema"; /** Provider key owned by the custom-webhook adapter, outside the provider-neutral kernel. */ export const webhookProviderKey = IntegrationProviderKey.make("webhook"); +export const webhookEventsPostCapabilityKey = + IntegrationCapabilityKey.make("events.post"); /** Browser-safe provider key for the only V1 integration provider. */ export const WebhookProviderKey = Schema.Literal("webhook"); @@ -31,7 +33,7 @@ export const webhookProviderManifest = IntegrationProviderManifest.make({ connectionMode: "none", capabilities: [ { - key: IntegrationCapabilityKey.make("events.post"), + key: webhookEventsPostCapabilityKey, direction: "outbound", configVersion: 1, }, diff --git a/integrations/webhook/src/webhook-provider-registration.test.ts b/integrations/webhook/src/webhook-provider-registration.test.ts index 61631d51..56f17a5c 100644 --- a/integrations/webhook/src/webhook-provider-registration.test.ts +++ b/integrations/webhook/src/webhook-provider-registration.test.ts @@ -20,7 +20,7 @@ import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; import { Webhook } from "standardwebhooks"; import { startTestServer } from "./test-server"; -import { webhookProviderKey } from "./webhook-manifest"; +import { webhookEventsPostCapabilityKey, webhookProviderKey } from "./webhook-manifest"; import { WebhookExternalPayload } from "./webhook-payload"; import { makeWebhookProviderRegistration } from "./webhook-provider-registration"; @@ -102,7 +102,7 @@ const makeDeliveryFixture = () => version: 1, }, route: { - capabilityKey: "events.post", + capabilityKey: webhookEventsPostCapabilityKey, configVersion: 1, connectionId, enabled: true, @@ -148,7 +148,7 @@ describe("webhook provider registration", () => { expect( registry.getHandler({ provider: webhookProviderKey, - capabilityKey: "events.post", + capabilityKey: webhookEventsPostCapabilityKey, }) ).toBeDefined(); }); diff --git a/integrations/webhook/src/webhook-provider-registration.ts b/integrations/webhook/src/webhook-provider-registration.ts index 13121b6c..23bf92a8 100644 --- a/integrations/webhook/src/webhook-provider-registration.ts +++ b/integrations/webhook/src/webhook-provider-registration.ts @@ -20,6 +20,7 @@ import { import { WebhookConnectionConfiguration, WebhookRouteConfiguration, + webhookEventsPostCapabilityKey, webhookProviderKey, webhookProviderManifest, } from "./webhook-manifest"; @@ -55,7 +56,7 @@ export const makeWebhookProviderRegistration = ({ connectionConfigurationSchema: WebhookConnectionConfiguration, handlers: [ { - capabilityKey: "events.post", + capabilityKey: webhookEventsPostCapabilityKey, deliver: (input) => Effect.gen(function* () { const credentials = @@ -203,6 +204,6 @@ export const makeWebhookProviderRegistration = ({ inboundHandlers: [], manifest: webhookProviderManifest, routeConfigurationSchemas: new Map([ - ["events.post", WebhookRouteConfiguration], + [webhookEventsPostCapabilityKey, WebhookRouteConfiguration], ]), }); diff --git a/packages/auth/src/adapter/drizzle-adapter-reference.ts b/packages/auth/src/adapter/drizzle-adapter-reference.ts index b187da72..77bb3b4a 100644 --- a/packages/auth/src/adapter/drizzle-adapter-reference.ts +++ b/packages/auth/src/adapter/drizzle-adapter-reference.ts @@ -187,13 +187,7 @@ export const drizzleAdapter = (db: DB, config: DrizzleAdapterConfig) => { let mysqlNoIdWarned = false; const createCustomAdapter = (db: DB, inTransaction = false): AdapterFactoryCustomizeAdapterCreator => - ({ - getFieldName, - getDefaultFieldName, - getDefaultModelName, - options, - schema: baSchema, - }) => { + ({ getFieldName, getDefaultModelName, options, schema: baSchema }) => { if ( config.provider === "mysql" && options.advanced?.database?.generateId === false && diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index f7e17899..549c90e0 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -9,3 +9,4 @@ export { DEFAULT_POST_EMBEDDING_DIMENSIONS, ROADMAP_PRIMARY_ORGANIZATION_ID_UIDX, } from "./schema/feedback"; +export { gitHubIssueSafeMetadataConditions } from "./schema/integration"; diff --git a/packages/db/src/migrations/20260814183627_young_nicolaos/migration.sql b/packages/db/src/migrations/20260814183627_young_nicolaos/migration.sql new file mode 100644 index 00000000..1387e2bf --- /dev/null +++ b/packages/db/src/migrations/20260814183627_young_nicolaos/migration.sql @@ -0,0 +1,103 @@ +CREATE TABLE "external_resource_create_request" ( + "id" text PRIMARY KEY, + "organization_id" text NOT NULL, + "connection_id" text NOT NULL, + "post_id" text NOT NULL, + "idempotency_key" text NOT NULL, + "state" text NOT NULL, + "external_resource_id" text, + "post_external_resource_link_id" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "github_installation" ( + "connection_id" text PRIMARY KEY, + "installation_id" text NOT NULL, + "account_id" text NOT NULL, + "account_login" text NOT NULL, + "account_type" text NOT NULL, + "suspended_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "github_sync_rule" ( + "id" text PRIMARY KEY, + "organization_id" text NOT NULL, + "connection_id" text NOT NULL, + "issue_match_mode" text NOT NULL, + "issue_state" text NOT NULL, + "post_status_id" text NOT NULL, + "upvoter_notification_policy" text NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "github_sync_rule_combo_ck" CHECK (("issue_match_mode" = 'any' AND "issue_state" = 'open') OR ("issue_match_mode" = 'all' AND "issue_state" = 'closed')) +); +--> statement-breakpoint +CREATE TABLE "github_webhook_delivery" ( + "id" text PRIMARY KEY, + "connection_id" text NOT NULL, + "delivery_id" text NOT NULL, + "event_name" text NOT NULL, + "received_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "integration_external_resource" ( + "id" text PRIMARY KEY, + "organization_id" text NOT NULL, + "connection_id" text NOT NULL, + "resource_type" text NOT NULL, + "remote_id" text NOT NULL, + "remote_url" text NOT NULL, + "display_key" text, + "title" text, + "state_key" text, + "safe_metadata" jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "post_external_resource_link" ( + "id" text PRIMARY KEY, + "organization_id" text NOT NULL, + "post_id" text NOT NULL, + "external_resource_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX "post_status_organizationId_id_uidx" ON "post_status" ("organization_id","id");--> statement-breakpoint +CREATE UNIQUE INDEX "external_resource_create_request_connection_key_uidx" ON "external_resource_create_request" ("connection_id","idempotency_key");--> statement-breakpoint +CREATE UNIQUE INDEX "github_installation_installation_id_uidx" ON "github_installation" ("installation_id");--> statement-breakpoint +CREATE INDEX "github_installation_account_idx" ON "github_installation" ("account_id");--> statement-breakpoint +CREATE UNIQUE INDEX "github_sync_rule_open_connection_uq" ON "github_sync_rule" ("connection_id") WHERE "issue_match_mode" = 'any' AND "issue_state" = 'open';--> statement-breakpoint +CREATE UNIQUE INDEX "github_sync_rule_closed_connection_uq" ON "github_sync_rule" ("connection_id") WHERE "issue_match_mode" = 'all' AND "issue_state" = 'closed';--> statement-breakpoint +CREATE INDEX "github_sync_rule_connection_enabled_idx" ON "github_sync_rule" ("connection_id","enabled");--> statement-breakpoint +CREATE INDEX "github_sync_rule_organization_idx" ON "github_sync_rule" ("organization_id");--> statement-breakpoint +CREATE UNIQUE INDEX "github_webhook_delivery_connection_delivery_uidx" ON "github_webhook_delivery" ("connection_id","delivery_id");--> statement-breakpoint +CREATE UNIQUE INDEX "integration_external_resource_organization_id_uidx" ON "integration_external_resource" ("organization_id","id");--> statement-breakpoint +CREATE UNIQUE INDEX "integration_external_resource_connection_type_remote_uidx" ON "integration_external_resource" ("connection_id","resource_type","remote_id");--> statement-breakpoint +CREATE INDEX "integration_external_resource_organization_connection_idx" ON "integration_external_resource" ("organization_id","connection_id");--> statement-breakpoint +CREATE UNIQUE INDEX "integration_route_connection_id_uidx" ON "integration_route" ("connection_id","id");--> statement-breakpoint +CREATE UNIQUE INDEX "post_external_resource_link_organization_id_uidx" ON "post_external_resource_link" ("organization_id","id");--> statement-breakpoint +CREATE UNIQUE INDEX "post_external_resource_link_post_resource_uidx" ON "post_external_resource_link" ("post_id","external_resource_id");--> statement-breakpoint +CREATE INDEX "post_external_resource_link_organization_post_idx" ON "post_external_resource_link" ("organization_id","post_id");--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_eU77wOAyFsGn_fkey" FOREIGN KEY ("organization_id") REFERENCES "organization"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_organization_connection_fkey" FOREIGN KEY ("organization_id","connection_id") REFERENCES "integration_connection"("organization_id","id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_post_organization_fkey" FOREIGN KEY ("post_id","organization_id") REFERENCES "post"("id","organization_id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_resource_fkey" FOREIGN KEY ("external_resource_id") REFERENCES "integration_external_resource"("id") ON DELETE SET NULL;--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_link_fkey" FOREIGN KEY ("post_external_resource_link_id") REFERENCES "post_external_resource_link"("id") ON DELETE SET NULL;--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_organization_resource_fkey" FOREIGN KEY ("organization_id","external_resource_id") REFERENCES "integration_external_resource"("organization_id","id");--> statement-breakpoint +ALTER TABLE "external_resource_create_request" ADD CONSTRAINT "external_resource_create_request_organization_link_fkey" FOREIGN KEY ("organization_id","post_external_resource_link_id") REFERENCES "post_external_resource_link"("organization_id","id");--> statement-breakpoint +ALTER TABLE "github_installation" ADD CONSTRAINT "github_installation_hxjCxRRkLkaP_fkey" FOREIGN KEY ("connection_id") REFERENCES "integration_connection"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "github_sync_rule" ADD CONSTRAINT "github_sync_rule_organization_id_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organization"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "github_sync_rule" ADD CONSTRAINT "github_sync_rule_organization_connection_fkey" FOREIGN KEY ("organization_id","connection_id") REFERENCES "integration_connection"("organization_id","id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "github_sync_rule" ADD CONSTRAINT "github_sync_rule_organization_status_fkey" FOREIGN KEY ("organization_id","post_status_id") REFERENCES "post_status"("organization_id","id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "github_webhook_delivery" ADD CONSTRAINT "github_webhook_delivery_XC1Ae9VBXiU3_fkey" FOREIGN KEY ("connection_id") REFERENCES "integration_connection"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "integration_delivery" ADD CONSTRAINT "integration_delivery_connection_route_fkey" FOREIGN KEY ("connection_id","route_id") REFERENCES "integration_route"("connection_id","id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "integration_external_resource" ADD CONSTRAINT "integration_external_resource_DDcoUtAHSqcr_fkey" FOREIGN KEY ("organization_id") REFERENCES "organization"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "integration_external_resource" ADD CONSTRAINT "integration_external_resource_organization_connection_fkey" FOREIGN KEY ("organization_id","connection_id") REFERENCES "integration_connection"("organization_id","id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "post_external_resource_link" ADD CONSTRAINT "post_external_resource_link_ywAvLx23F2Vs_fkey" FOREIGN KEY ("organization_id") REFERENCES "organization"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "post_external_resource_link" ADD CONSTRAINT "post_external_resource_link_post_organization_fkey" FOREIGN KEY ("post_id","organization_id") REFERENCES "post"("id","organization_id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "post_external_resource_link" ADD CONSTRAINT "post_external_resource_link_organization_resource_fkey" FOREIGN KEY ("organization_id","external_resource_id") REFERENCES "integration_external_resource"("organization_id","id") ON DELETE CASCADE; \ No newline at end of file diff --git a/packages/db/src/migrations/20260814183627_young_nicolaos/snapshot.json b/packages/db/src/migrations/20260814183627_young_nicolaos/snapshot.json new file mode 100644 index 00000000..30d21938 --- /dev/null +++ b/packages/db/src/migrations/20260814183627_young_nicolaos/snapshot.json @@ -0,0 +1,14118 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "28ee586a-4359-4fc1-b522-aee29614a7c7", + "prevIds": [ + "36ae8855-0542-4b1e-b9ad-1112e7da0a63" + ], + "ddl": [ + { + "values": [ + "PUBLIC", + "PRIVATE" + ], + "name": "board_visibility", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "draft", + "scheduled", + "published" + ], + "name": "changelog_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "PUBLIC", + "HIDDEN" + ], + "name": "changelog_visibility", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "PUBLIC", + "INTERNAL" + ], + "name": "post_comment_visibility", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "PUBLIC", + "HIDDEN" + ], + "name": "roadmap_visibility", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "public", + "private" + ], + "name": "saved_roadmap_visibility", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "account", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "invitation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jwt_secret", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "member", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organization", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "product", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "session", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "subscription", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "two_factor", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "verification", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "board", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "changelog_category_link", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "changelog_category", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "changelog_post", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "changelog", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "changelog_tag", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "comment_reaction", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "comment", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "company_attribute_definition", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "company_attribute_value", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "company", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "contact_attribute_definition", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "contact_attribute_value", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "contact", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "email_contact", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "email_delivery", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "email_outbox", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "email_provider_event", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "email_subscription", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "email_suppression", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "notification", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_activity", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_reaction", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_status", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_subscription", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_tag", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "roadmap_column", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "roadmap", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "site", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "submission_notification_batch", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "submission_notification_queue", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tag", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "upvote", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "external_resource_create_request", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_installation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_sync_rule", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "github_webhook_delivery", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "integration_connection", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "integration_delivery_attempt", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "integration_delivery", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "integration_event", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "integration_external_resource", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "integration_route", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_external_resource_link", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "asset", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "changelog_asset", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "post_asset", + "entityType": "tables", + "schema": "public" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "issuer", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scope", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inviter_id", + "entityType": "columns", + "schema": "public", + "table": "invitation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jwt_secret" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "jwt_secret" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "jwt_secret" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "jwt_secret" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "revoked_at", + "entityType": "columns", + "schema": "public", + "table": "jwt_secret" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'manager'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "member" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "logo", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "organization" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trial_interval", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trial_interval_count", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recurring_interval", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recurring_interval_count", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "is_recurring", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "is_archived", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_organization_id", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "visibility", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prices", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "product" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_address", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_agent", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "impersonated_by", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "active_organization_id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "real", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "amount", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cancel_at_period_end", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "currency", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recurring_interval", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recurring_interval_count", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "current_period_start", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "current_period_end", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trial_start", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trial_end", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "canceled_at", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ends_at", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ended_at", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "customer_id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "product_id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "discount_id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "checkout_id", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "seats", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "backup_codes", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "two_factor" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "email_verified", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "timezone", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "image", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "banned", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ban_reason", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ban_expires", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "two_factor_enabled", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_login_method", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "jwt_auto_login_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email_hash", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "restricted_to_organization_id", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "identifier", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "board_visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "visibility", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_id", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_member_id", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "board" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "changelog_category_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changelog_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_category_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "category_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_category_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_category_link" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_category_link" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_category_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'color'", + "generated": null, + "identity": null, + "name": "icon_type", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "icon", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_category" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changelog_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cover_image", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "excerpt", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "changelog_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scheduled_at", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "published_at", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_id", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_member_id", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "changelog" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "changelog_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changelog_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tag_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_tag" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_tag" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "changelog_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_id", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "comment_id", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "emoji", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "comment_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_id", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "post_comment_visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'PUBLIC'", + "generated": null, + "identity": null, + "name": "visibility", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "parent_comment_id", + "entityType": "columns", + "schema": "public", + "table": "comment" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_required", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "company_id", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "attribute_id", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_text", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_integer", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "real", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_decimal", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_boolean", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_date", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "company_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_id", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatar", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_created_at", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'DASHBOARD'", + "generated": null, + "identity": null, + "name": "source", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "company" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_required", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "contact_id", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "attribute_id", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_text", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_integer", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "real", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_decimal", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_boolean", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value_date", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "phone", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_id", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatar", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "company_id", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'DASHBOARD'", + "generated": null, + "identity": null, + "name": "source", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verification_state", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "email_contact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outbox_id", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "contact_id", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recipient_email", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "template", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "template_version", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "template_payload", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message_id", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "attempt_count", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "next_attempt_at", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accepted_at", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivered_at", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_error", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_metadata", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "email_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "aggregate_type", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "aggregate_id", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deduplication_key", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scheduled_at", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "email_outbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_event_id", + "entityType": "columns", + "schema": "public", + "table": "email_provider_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivery_id", + "entityType": "columns", + "schema": "public", + "table": "email_provider_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "email_provider_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "occurred_at", + "entityType": "columns", + "schema": "public", + "table": "email_provider_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "received_at", + "entityType": "columns", + "schema": "public", + "table": "email_provider_event" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "email_provider_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "contact_id", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "topic_type", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "topic_id", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verification_token_hash", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verification_expires_at", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "unsubscribe_token_hash", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "unsubscribed_at", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "email_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "email_suppression" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reason", + "entityType": "columns", + "schema": "public", + "table": "email_suppression" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_event_id", + "entityType": "columns", + "schema": "public", + "table": "email_suppression" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "email_suppression" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recipient_member_id", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actor_member_id", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_type", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_id", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "body", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "href", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deduplication_key", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "read_at", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "notification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actor_id", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actor_member_id", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "previous_value", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "next_value", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "comment_id", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post_activity" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_id", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "emoji", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "post_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post_status" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "post_status" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "order_index", + "entityType": "columns", + "schema": "public", + "table": "post_status" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "post_status" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post_status" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "post_status" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_id", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "post_subscription" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "excerpt", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "board_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status_schema_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "eta_quarter", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_member_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "contact_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'DASHBOARD'", + "generated": null, + "identity": null, + "name": "source", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "metadata", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locked_at", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "archived_at", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "merged_into_post_id", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "merged_at", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "vector(1536)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "embedding", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "embedding_model", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "embedded_at", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "post" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "post_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tag_id", + "entityType": "columns", + "schema": "public", + "table": "post_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "post_tag" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post_tag" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "post_tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "roadmap_id", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "position", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "roadmap_column" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_primary", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "mode", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "saved_roadmap_visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "visibility", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "filter", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "roadmap" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subdomain", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "custom_domain", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "changelog_visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'PUBLIC'", + "generated": null, + "identity": null, + "name": "changelog_visibility", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "roadmap_visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'PUBLIC'", + "generated": null, + "identity": null, + "name": "roadmap_visibility", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "no_index", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "hide_powered_by", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "site" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "submission_notification_batch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "submission_notification_batch" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "submission_notification_batch" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "submission_notification_queue" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "submission_notification_queue" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "submission_notification_queue" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_id", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creator_member_id", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "tag" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_id", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "upvote" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "idempotency_key", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_resource_id", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_external_resource_link_id", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "installation_id", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_id", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_login", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_type", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "suspended_at", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_installation" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "issue_match_mode", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "issue_state", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_status_id", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "upvoter_notification_policy", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "enabled", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "github_sync_rule" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivery_id", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_name", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "received_at", + "entityType": "columns", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remote_account_id", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lifecycle", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "credential_generation", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credentials_ciphertext", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "safe_display_metadata", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "consecutive_exhausted_deliveries", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_succeeded_at", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_failed_at", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "archived_at", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retention_expires_at", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "integration_connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivery_id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "attempt_number", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "finished_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "duration_ms", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "http_status", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "error_tag", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retry_decision", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "diagnostics", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retention_expires_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "route_id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_id", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action_key", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ordering_key", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "attempt_count", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "next_attempt_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lease_owner", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lease_expires_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "succeeded_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "exhausted_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "canceled_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_error", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retention_expires_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "integration_delivery" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "version", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "occurred_at", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "origin", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "causation_id", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "correlation_id", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "causal_hop_count", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "retention_expires_at", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "integration_event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_type", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remote_id", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remote_url", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "display_key", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state_key", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "safe_metadata", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "integration_external_resource" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "capability_key", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "enabled", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_types", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "route_key", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config_version", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_config", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "safe_display_metadata", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "integration_route" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "external_resource_id", + "entityType": "columns", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bucket", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "url", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changelog_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "asset_id", + "entityType": "columns", + "schema": "public", + "table": "changelog_asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "post_id", + "entityType": "columns", + "schema": "public", + "table": "post_asset" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "asset_id", + "entityType": "columns", + "schema": "public", + "table": "post_asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "account_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "issuer", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "account_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "account_issuer_account_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "invitation_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "invitation_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "jwt_secret_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "jwt_secret" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"revoked_at\" is null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "jwt_secret_organizationId_active_uidx", + "entityType": "indexes", + "schema": "public", + "table": "jwt_secret" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_organizationId_userId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organization_slug_uidx", + "entityType": "indexes", + "schema": "public", + "table": "organization" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "session_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "twoFactor_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "two_factor" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "twoFactor_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "two_factor" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_emailHash_idx", + "entityType": "indexes", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "restricted_to_organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_restricted_to_organization_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "identifier", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "verification_identifier_idx", + "entityType": "indexes", + "schema": "public", + "table": "verification" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "board_organizationId_slug_uidx", + "entityType": "indexes", + "schema": "public", + "table": "board" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "changelog_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_category_link_changelogId_idx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_category_link" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "category_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_category_link_categoryId_idx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_category_link" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "changelog_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "category_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_category_link_changelogId_categoryId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_category_link" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_category_organizationId_name_uidx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_category" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_post_postId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_post_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_organizationId_slug_uidx", + "entityType": "indexes", + "schema": "public", + "table": "changelog" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "changelog_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_tag_changelogId_idx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tag_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_tag_tagId_idx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "changelog_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tag_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_tag_changelogId_tagId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "comment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "emoji", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "commentReaction_userId_commentId_emoji_uidx", + "entityType": "indexes", + "schema": "public", + "table": "comment_reaction" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_attribute_definition_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_attribute_definition_organizationId_key_uidx", + "entityType": "indexes", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_attribute_value_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "company_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_attribute_value_companyId_idx", + "entityType": "indexes", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "attribute_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_attribute_value_attributeId_idx", + "entityType": "indexes", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "company_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "attribute_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_attribute_value_companyId_attributeId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "company" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "external_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_organizationId_externalId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "company" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "company_organizationId_name_uidx", + "entityType": "indexes", + "schema": "public", + "table": "company" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_attribute_definition_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_attribute_definition_organizationId_key_uidx", + "entityType": "indexes", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_attribute_value_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "contact_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_attribute_value_contactId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "attribute_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_attribute_value_attributeId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "contact_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "attribute_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_attribute_value_contactId_attributeId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "company_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_companyId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "external_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_organizationId_externalId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "contact_organizationId_email_uidx", + "entityType": "indexes", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_contact_organizationId_email_uidx", + "entityType": "indexes", + "schema": "public", + "table": "email_contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_contact_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_contact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "outbox_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "recipient_email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_delivery_outboxId_recipientEmail_uidx", + "entityType": "indexes", + "schema": "public", + "table": "email_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "message_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_delivery_messageId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "email_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "next_attempt_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_delivery_state_nextAttemptAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "contact_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_delivery_contactId_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "deduplication_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_outbox_organizationId_deduplicationKey_uidx", + "entityType": "indexes", + "schema": "public", + "table": "email_outbox" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "scheduled_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_outbox_state_scheduledAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_outbox" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_outbox_organizationId_state_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_outbox" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "kind", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "aggregate_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"state\" = 'pending' AND \"kind\" = 'post.status_changed'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_outbox_pendingStatusAggregate_uidx", + "entityType": "indexes", + "schema": "public", + "table": "email_outbox" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "delivery_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "occurred_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_provider_event_deliveryId_occurredAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_provider_event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_subscription_organizationId_state_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_subscription" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "topic_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "topic_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "contact_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_subscription_recipientLookup_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_subscription" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_subscription_state_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "email_subscription" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "provider_event_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "email_suppression_providerEventId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "email_suppression" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "recipient_member_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "read_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "notification_recipient_read_created_idx", + "entityType": "indexes", + "schema": "public", + "table": "notification" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "notification_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "notification" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "recipient_member_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "deduplication_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "notification_recipient_deduplication_uidx", + "entityType": "indexes", + "schema": "public", + "table": "notification" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_activity_postId_createdAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_activity" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_activity_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_activity" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "emoji", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "postReaction_userId_postId_emoji_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_reaction" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_status_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_status" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_status_organizationId_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_status" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_status_organizationId_type_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_status" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "order_index", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_status_organizationId_orderIndex_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_status" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_subscription_postId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_subscription_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_subscription_postId_userId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status_schema_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_statusId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "archived_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_archivedAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "merged_into_post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_mergedIntoPostId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "embedding", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": { + "name": "vector_cosine_ops", + "default": false + } + } + ], + "isUnique": false, + "where": "\"embedding\" is not null", + "with": "", + "method": "hnsw", + "concurrently": false, + "name": "post_embedding_hnsw_idx", + "entityType": "indexes", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_id_organizationId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_organizationId_slug_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_tag_postId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tag_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_tag_tagId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tag_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_tag_postId_tagId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "roadmap_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "roadmap_column_roadmapId_idx", + "entityType": "indexes", + "schema": "public", + "table": "roadmap_column" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "roadmap_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "position", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "roadmap_column_roadmapId_position_idx", + "entityType": "indexes", + "schema": "public", + "table": "roadmap_column" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "roadmap_organizationId_slug_uidx", + "entityType": "indexes", + "schema": "public", + "table": "roadmap" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "roadmap_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "roadmap" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"is_primary\"", + "with": "", + "method": "btree", + "concurrently": false, + "name": "roadmap_primary_organizationId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "roadmap" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "site_organizationId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "site" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "subdomain", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "site_subdomain_uidx", + "entityType": "indexes", + "schema": "public", + "table": "site" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "submission_notification_queue_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "submission_notification_queue" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tag_organizationId_type_name_uidx", + "entityType": "indexes", + "schema": "public", + "table": "tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tag_organizationId_type_slug_uidx", + "entityType": "indexes", + "schema": "public", + "table": "tag" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "upvote_postId_idx", + "entityType": "indexes", + "schema": "public", + "table": "upvote" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "upvote_userId_postId_uidx", + "entityType": "indexes", + "schema": "public", + "table": "upvote" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "idempotency_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "external_resource_create_request_connection_key_uidx", + "entityType": "indexes", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "installation_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_installation_installation_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "account_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_installation_account_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"issue_match_mode\" = 'any' AND \"issue_state\" = 'open'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_sync_rule_open_connection_uq", + "entityType": "indexes", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"issue_match_mode\" = 'all' AND \"issue_state\" = 'closed'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_sync_rule_closed_connection_uq", + "entityType": "indexes", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "enabled", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_sync_rule_connection_enabled_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_sync_rule_organization_idx", + "entityType": "indexes", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "delivery_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "github_webhook_delivery_connection_delivery_uidx", + "entityType": "indexes", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "provider", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_connection_organization_provider_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "lifecycle", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_connection_organization_lifecycle_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_connection_organization_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "provider", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "remote_account_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"provider\" = 'discord' and \"remote_account_id\" is not null and \"lifecycle\" = 'active'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_connection_provider_remote_account_active_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "delivery_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "attempt_number", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_attempt_delivery_number_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "delivery_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "started_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_attempt_delivery_started_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "retention_expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_attempt_retention_expires_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "route_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "event_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "action_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_route_event_action_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "next_attempt_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "lease_expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_lease_claim_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_connection_state_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_organization_state_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "retention_expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_delivery_retention_expires_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "occurred_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_event_organization_occurred_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "retention_expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_event_retention_expires_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_event_organization_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_external_resource_organization_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_external_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "resource_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "remote_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_external_resource_connection_type_remote_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_external_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_external_resource_organization_connection_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_external_resource" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "enabled", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_route_connection_enabled_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "enabled", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_route_organization_enabled_idx", + "entityType": "indexes", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_route_organization_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_route_connection_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "connection_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "capability_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "route_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "integration_route_connection_capability_key_uidx", + "entityType": "indexes", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_external_resource_link_organization_id_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "external_resource_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_external_resource_link_post_resource_uidx", + "entityType": "indexes", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "post_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_external_resource_link_organization_post_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "bucket", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "asset_key_uidx", + "entityType": "indexes", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "COALESCE(\"user_id\", \"organization_id\")", + "isExpression": true, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "kind", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"kind\" IN ('profile_image', 'organization_logo')", + "with": "", + "method": "btree", + "concurrently": false, + "name": "asset_owner_kind_singleton_uidx", + "entityType": "indexes", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "asset_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "asset_organizationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "url", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "asset_url_idx", + "entityType": "indexes", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "asset_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "changelog_asset_assetId_idx", + "entityType": "indexes", + "schema": "public", + "table": "changelog_asset" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "asset_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "post_asset_assetId_idx", + "entityType": "indexes", + "schema": "public", + "table": "post_asset" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "account_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "invitation_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": false, + "columns": [ + "inviter_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "invitation_inviter_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "invitation" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "jwt_secret_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "jwt_secret" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "member_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "member_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "member" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "session_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "subscription_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "subscription" + }, + { + "nameExplicit": false, + "columns": [ + "product_id" + ], + "schemaTo": "public", + "tableTo": "product", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "subscription_product_id_product_id_fk", + "entityType": "fks", + "schema": "public", + "table": "subscription" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "two_factor_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "two_factor" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "board_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "board" + }, + { + "nameExplicit": false, + "columns": [ + "creator_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "board_creator_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "board" + }, + { + "nameExplicit": false, + "columns": [ + "creator_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "board_creator_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "board" + }, + { + "nameExplicit": false, + "columns": [ + "changelog_id" + ], + "schemaTo": "public", + "tableTo": "changelog", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_category_link_changelog_id_changelog_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_category_link" + }, + { + "nameExplicit": false, + "columns": [ + "category_id" + ], + "schemaTo": "public", + "tableTo": "changelog_category", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_category_link_category_id_changelog_category_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_category_link" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_category_link_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_category_link" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_category_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_category" + }, + { + "nameExplicit": false, + "columns": [ + "changelog_id" + ], + "schemaTo": "public", + "tableTo": "changelog", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_post_changelog_id_changelog_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_post" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_post_post_id_post_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_post" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_post_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_post" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "changelog" + }, + { + "nameExplicit": false, + "columns": [ + "creator_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "changelog_creator_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "changelog" + }, + { + "nameExplicit": false, + "columns": [ + "creator_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "changelog_creator_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "changelog" + }, + { + "nameExplicit": false, + "columns": [ + "changelog_id" + ], + "schemaTo": "public", + "tableTo": "changelog", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_tag_changelog_id_changelog_id_fk", + "entityType": "fks", + "schema": "public", + "table": "changelog_tag" + }, + { + "nameExplicit": false, + "columns": [ + "tag_id" + ], + "schemaTo": "public", + "tableTo": "tag", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_tag_tag_id_tag_id_fk", + "entityType": "fks", + "schema": "public", + "table": "changelog_tag" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_tag_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "changelog_tag" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comment_reaction_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "comment_reaction_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "comment_id" + ], + "schemaTo": "public", + "tableTo": "comment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comment_reaction_comment_id_comment_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comment_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comment_post_id_post_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comment_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment" + }, + { + "nameExplicit": false, + "columns": [ + "member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "comment_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment" + }, + { + "nameExplicit": false, + "columns": [ + "parent_comment_id" + ], + "schemaTo": "public", + "tableTo": "comment", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comment_parent_comment_id_comment_id_fk", + "entityType": "fks", + "schema": "public", + "table": "comment" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "company_attribute_definition_kTIdzOEhY84w_fkey", + "entityType": "fks", + "schema": "public", + "table": "company_attribute_definition" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "company_attribute_value_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": false, + "columns": [ + "company_id" + ], + "schemaTo": "public", + "tableTo": "company", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "company_attribute_value_company_id_company_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": false, + "columns": [ + "attribute_id" + ], + "schemaTo": "public", + "tableTo": "company_attribute_definition", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "company_attribute_value_llKdT8uQbpgY_fkey", + "entityType": "fks", + "schema": "public", + "table": "company_attribute_value" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "company_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "company" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "contact_attribute_definition_kTIdzMCiJSi5_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact_attribute_definition" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "contact_attribute_value_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": false, + "columns": [ + "contact_id" + ], + "schemaTo": "public", + "tableTo": "contact", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "contact_attribute_value_contact_id_contact_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": false, + "columns": [ + "attribute_id" + ], + "schemaTo": "public", + "tableTo": "contact_attribute_definition", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "contact_attribute_value_4kkzVjc1dj7o_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact_attribute_value" + }, + { + "nameExplicit": false, + "columns": [ + "company_id" + ], + "schemaTo": "public", + "tableTo": "company", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "contact_company_id_company_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "contact_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "contact_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "contact" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "email_contact_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_contact" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "email_contact_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_contact" + }, + { + "nameExplicit": false, + "columns": [ + "outbox_id" + ], + "schemaTo": "public", + "tableTo": "email_outbox", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "email_delivery_outbox_id_email_outbox_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_delivery" + }, + { + "nameExplicit": false, + "columns": [ + "contact_id" + ], + "schemaTo": "public", + "tableTo": "email_contact", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "email_delivery_contact_id_email_contact_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_delivery" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "email_outbox_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_outbox" + }, + { + "nameExplicit": false, + "columns": [ + "delivery_id" + ], + "schemaTo": "public", + "tableTo": "email_delivery", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "email_provider_event_delivery_id_email_delivery_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_provider_event" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "email_subscription_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "contact_id" + ], + "schemaTo": "public", + "tableTo": "email_contact", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "email_subscription_contact_id_email_contact_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "email_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "notification_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "notification" + }, + { + "nameExplicit": false, + "columns": [ + "recipient_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "notification_recipient_member_id_member_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "notification" + }, + { + "nameExplicit": false, + "columns": [ + "actor_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "notification_actor_member_id_member_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "notification" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_activity_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_activity" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_activity_post_id_post_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_activity" + }, + { + "nameExplicit": false, + "columns": [ + "actor_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_activity_actor_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_activity" + }, + { + "nameExplicit": false, + "columns": [ + "actor_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_activity_actor_member_id_member_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_activity" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_reaction_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_reaction_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_reaction_post_id_post_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_status_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_status" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_subscription_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_subscription_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_subscription_post_id_post_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_subscription_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "board_id" + ], + "schemaTo": "public", + "tableTo": "board", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_board_id_board_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": false, + "columns": [ + "status_schema_id" + ], + "schemaTo": "public", + "tableTo": "post_status", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "post_status_schema_id_post_status_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": false, + "columns": [ + "creator_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_creator_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": false, + "columns": [ + "creator_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_creator_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": false, + "columns": [ + "contact_id" + ], + "schemaTo": "public", + "tableTo": "contact", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "post_contact_id_contact_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": true, + "columns": [ + "merged_into_post_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id", + "organization_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "post_merged_into_same_organization_fk", + "entityType": "fks", + "schema": "public", + "table": "post" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_tag_post_id_post_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_tag" + }, + { + "nameExplicit": false, + "columns": [ + "tag_id" + ], + "schemaTo": "public", + "tableTo": "tag", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_tag_tag_id_tag_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_tag" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_tag_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "post_tag" + }, + { + "nameExplicit": false, + "columns": [ + "roadmap_id" + ], + "schemaTo": "public", + "tableTo": "roadmap", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "roadmap_column_roadmap_id_roadmap_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "roadmap_column" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "roadmap_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "roadmap" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "site_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "site" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "submission_notification_batch_kCxPdJxLS9cJ_fkey", + "entityType": "fks", + "schema": "public", + "table": "submission_notification_batch" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "submission_notification_queue_post_id_post_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "submission_notification_queue" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "submission_notification_queue_54LYiNshYitK_fkey", + "entityType": "fks", + "schema": "public", + "table": "submission_notification_queue" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tag_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "tag" + }, + { + "nameExplicit": false, + "columns": [ + "creator_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "tag_creator_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "tag" + }, + { + "nameExplicit": false, + "columns": [ + "creator_member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "tag_creator_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "tag" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "upvote_user_id_user_id_fk", + "entityType": "fks", + "schema": "public", + "table": "upvote" + }, + { + "nameExplicit": false, + "columns": [ + "member_id" + ], + "schemaTo": "public", + "tableTo": "member", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "upvote_member_id_member_id_fk", + "entityType": "fks", + "schema": "public", + "table": "upvote" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "upvote_post_id_post_id_fk", + "entityType": "fks", + "schema": "public", + "table": "upvote" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "upvote_organization_id_organization_id_fk", + "entityType": "fks", + "schema": "public", + "table": "upvote" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "external_resource_create_request_eU77wOAyFsGn_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "external_resource_create_request_organization_connection_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + "post_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id", + "organization_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "external_resource_create_request_post_organization_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + "external_resource_id" + ], + "schemaTo": "public", + "tableTo": "integration_external_resource", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "external_resource_create_request_resource_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + "post_external_resource_link_id" + ], + "schemaTo": "public", + "tableTo": "post_external_resource_link", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "external_resource_create_request_link_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "external_resource_id" + ], + "schemaTo": "public", + "tableTo": "integration_external_resource", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "external_resource_create_request_organization_resource_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "post_external_resource_link_id" + ], + "schemaTo": "public", + "tableTo": "post_external_resource_link", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "external_resource_create_request_organization_link_fkey", + "entityType": "fks", + "schema": "public", + "table": "external_resource_create_request" + }, + { + "nameExplicit": false, + "columns": [ + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_installation_hxjCxRRkLkaP_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_installation" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_sync_rule_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_sync_rule_organization_connection_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "post_status_id" + ], + "schemaTo": "public", + "tableTo": "post_status", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_sync_rule_organization_status_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_sync_rule" + }, + { + "nameExplicit": false, + "columns": [ + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "github_webhook_delivery_XC1Ae9VBXiU3_fkey", + "entityType": "fks", + "schema": "public", + "table": "github_webhook_delivery" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_connection_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_connection" + }, + { + "nameExplicit": false, + "columns": [ + "delivery_id" + ], + "schemaTo": "public", + "tableTo": "integration_delivery", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_delivery_attempt_xOqDtFxhvWcc_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_delivery_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_delivery_organization_connection_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "route_id" + ], + "schemaTo": "public", + "tableTo": "integration_route", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_delivery_organization_route_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + "connection_id", + "route_id" + ], + "schemaTo": "public", + "tableTo": "integration_route", + "columnsTo": [ + "connection_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_delivery_connection_route_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "event_id" + ], + "schemaTo": "public", + "tableTo": "integration_event", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_delivery_organization_event_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_delivery" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_event_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_event" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_external_resource_DDcoUtAHSqcr_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_external_resource" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_external_resource_organization_connection_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_external_resource" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_route_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "connection_id" + ], + "schemaTo": "public", + "tableTo": "integration_connection", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "integration_route_organization_connection_fkey", + "entityType": "fks", + "schema": "public", + "table": "integration_route" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_external_resource_link_ywAvLx23F2Vs_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "nameExplicit": true, + "columns": [ + "post_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id", + "organization_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_external_resource_link_post_organization_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "external_resource_id" + ], + "schemaTo": "public", + "tableTo": "integration_external_resource", + "columnsTo": [ + "organization_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_external_resource_link_organization_resource_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_external_resource_link" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "asset_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organization", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "asset_organization_id_organization_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "asset" + }, + { + "nameExplicit": false, + "columns": [ + "changelog_id" + ], + "schemaTo": "public", + "tableTo": "changelog", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_asset_changelog_id_changelog_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_asset" + }, + { + "nameExplicit": false, + "columns": [ + "asset_id" + ], + "schemaTo": "public", + "tableTo": "asset", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "changelog_asset_asset_id_asset_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "changelog_asset" + }, + { + "nameExplicit": false, + "columns": [ + "post_id" + ], + "schemaTo": "public", + "tableTo": "post", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_asset_post_id_post_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_asset" + }, + { + "nameExplicit": false, + "columns": [ + "asset_id" + ], + "schemaTo": "public", + "tableTo": "asset", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "post_asset_asset_id_asset_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "post_asset" + }, + { + "columns": [ + "changelog_id", + "post_id" + ], + "nameExplicit": false, + "name": "changelog_post_pkey", + "entityType": "pks", + "schema": "public", + "table": "changelog_post" + }, + { + "columns": [ + "changelog_id", + "asset_id" + ], + "nameExplicit": false, + "name": "changelog_asset_pkey", + "entityType": "pks", + "schema": "public", + "table": "changelog_asset" + }, + { + "columns": [ + "post_id", + "asset_id" + ], + "nameExplicit": false, + "name": "post_asset_pkey", + "entityType": "pks", + "schema": "public", + "table": "post_asset" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pkey", + "schema": "public", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "invitation_pkey", + "schema": "public", + "table": "invitation", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jwt_secret_pkey", + "schema": "public", + "table": "jwt_secret", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "member_pkey", + "schema": "public", + "table": "member", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "organization_pkey", + "schema": "public", + "table": "organization", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "product_pkey", + "schema": "public", + "table": "product", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pkey", + "schema": "public", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "subscription_pkey", + "schema": "public", + "table": "subscription", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "two_factor_pkey", + "schema": "public", + "table": "two_factor", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "user_pkey", + "schema": "public", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "verification_pkey", + "schema": "public", + "table": "verification", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "board_pkey", + "schema": "public", + "table": "board", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "changelog_category_link_pkey", + "schema": "public", + "table": "changelog_category_link", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "changelog_category_pkey", + "schema": "public", + "table": "changelog_category", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "changelog_pkey", + "schema": "public", + "table": "changelog", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "changelog_tag_pkey", + "schema": "public", + "table": "changelog_tag", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "comment_reaction_pkey", + "schema": "public", + "table": "comment_reaction", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "comment_pkey", + "schema": "public", + "table": "comment", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "company_attribute_definition_pkey", + "schema": "public", + "table": "company_attribute_definition", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "company_attribute_value_pkey", + "schema": "public", + "table": "company_attribute_value", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "company_pkey", + "schema": "public", + "table": "company", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "contact_attribute_definition_pkey", + "schema": "public", + "table": "contact_attribute_definition", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "contact_attribute_value_pkey", + "schema": "public", + "table": "contact_attribute_value", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "contact_pkey", + "schema": "public", + "table": "contact", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "email_contact_pkey", + "schema": "public", + "table": "email_contact", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "email_delivery_pkey", + "schema": "public", + "table": "email_delivery", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "email_outbox_pkey", + "schema": "public", + "table": "email_outbox", + "entityType": "pks" + }, + { + "columns": [ + "provider_event_id" + ], + "nameExplicit": false, + "name": "email_provider_event_pkey", + "schema": "public", + "table": "email_provider_event", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "email_subscription_pkey", + "schema": "public", + "table": "email_subscription", + "entityType": "pks" + }, + { + "columns": [ + "email" + ], + "nameExplicit": false, + "name": "email_suppression_pkey", + "schema": "public", + "table": "email_suppression", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "notification_pkey", + "schema": "public", + "table": "notification", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_activity_pkey", + "schema": "public", + "table": "post_activity", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_reaction_pkey", + "schema": "public", + "table": "post_reaction", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_status_pkey", + "schema": "public", + "table": "post_status", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_subscription_pkey", + "schema": "public", + "table": "post_subscription", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_pkey", + "schema": "public", + "table": "post", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_tag_pkey", + "schema": "public", + "table": "post_tag", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "roadmap_column_pkey", + "schema": "public", + "table": "roadmap_column", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "roadmap_pkey", + "schema": "public", + "table": "roadmap", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "site_pkey", + "schema": "public", + "table": "site", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "submission_notification_batch_pkey", + "schema": "public", + "table": "submission_notification_batch", + "entityType": "pks" + }, + { + "columns": [ + "post_id" + ], + "nameExplicit": false, + "name": "submission_notification_queue_pkey", + "schema": "public", + "table": "submission_notification_queue", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tag_pkey", + "schema": "public", + "table": "tag", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "upvote_pkey", + "schema": "public", + "table": "upvote", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "external_resource_create_request_pkey", + "schema": "public", + "table": "external_resource_create_request", + "entityType": "pks" + }, + { + "columns": [ + "connection_id" + ], + "nameExplicit": false, + "name": "github_installation_pkey", + "schema": "public", + "table": "github_installation", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "github_sync_rule_pkey", + "schema": "public", + "table": "github_sync_rule", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "github_webhook_delivery_pkey", + "schema": "public", + "table": "github_webhook_delivery", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "integration_connection_pkey", + "schema": "public", + "table": "integration_connection", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "integration_delivery_attempt_pkey", + "schema": "public", + "table": "integration_delivery_attempt", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "integration_delivery_pkey", + "schema": "public", + "table": "integration_delivery", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "integration_event_pkey", + "schema": "public", + "table": "integration_event", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "integration_external_resource_pkey", + "schema": "public", + "table": "integration_external_resource", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "integration_route_pkey", + "schema": "public", + "table": "integration_route", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "post_external_resource_link_pkey", + "schema": "public", + "table": "post_external_resource_link", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "asset_pkey", + "schema": "public", + "table": "asset", + "entityType": "pks" + }, + { + "nameExplicit": true, + "columns": [ + "organization_id", + "secret", + "revoked_at" + ], + "nullsNotDistinct": true, + "name": "jwt_secret_organizationId_secret_revokedAt_uidx", + "entityType": "uniques", + "schema": "public", + "table": "jwt_secret" + }, + { + "nameExplicit": true, + "columns": [ + "contact_id", + "topic_type", + "topic_id" + ], + "nullsNotDistinct": true, + "name": "email_subscription_contactId_topicType_topicId_uidx", + "entityType": "uniques", + "schema": "public", + "table": "email_subscription" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "organization_slug_unique", + "schema": "public", + "table": "organization", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "token" + ], + "nullsNotDistinct": false, + "name": "session_token_unique", + "schema": "public", + "table": "session", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "external_id" + ], + "nullsNotDistinct": false, + "name": "subscription_external_id_unique", + "schema": "public", + "table": "subscription", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "user_email_unique", + "schema": "public", + "table": "user", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "organization_id" + ], + "nullsNotDistinct": false, + "name": "submission_notification_batch_organization_id_key", + "schema": "public", + "table": "submission_notification_batch", + "entityType": "uniques" + }, + { + "value": "(\"merged_into_post_id\" is null and \"merged_at\" is null) or (\"merged_into_post_id\" is not null and \"merged_at\" is not null)", + "name": "post_merge_requires_target_and_timestamp_chk", + "entityType": "checks", + "schema": "public", + "table": "post" + }, + { + "value": "\"merged_into_post_id\" is null or \"archived_at\" is not null", + "name": "post_merged_rows_must_be_archived_chk", + "entityType": "checks", + "schema": "public", + "table": "post" + }, + { + "value": "\"merged_into_post_id\" is null or \"merged_into_post_id\" <> \"id\"", + "name": "post_no_self_merge_chk", + "entityType": "checks", + "schema": "public", + "table": "post" + }, + { + "value": "(\"embedding\" is null and \"embedding_model\" is null and \"embedded_at\" is null) or (\"embedding\" is not null and \"embedding_model\" is not null and \"embedded_at\" is not null)", + "name": "post_embedding_metadata_chk", + "entityType": "checks", + "schema": "public", + "table": "post" + }, + { + "value": "\"eta_quarter\" is null or \"eta_quarter\" ~ '^[0-9]{4}-Q[1-4]$'", + "name": "post_eta_quarter_format_chk", + "entityType": "checks", + "schema": "public", + "table": "post" + }, + { + "value": "(\"issue_match_mode\" = 'any' AND \"issue_state\" = 'open') OR (\"issue_match_mode\" = 'all' AND \"issue_state\" = 'closed')", + "name": "github_sync_rule_combo_ck", + "entityType": "checks", + "schema": "public", + "table": "github_sync_rule" + }, + { + "value": "\"credential_generation\" > 0", + "name": "integration_connection_credential_generation_check", + "entityType": "checks", + "schema": "public", + "table": "integration_connection" + }, + { + "value": "\"consecutive_exhausted_deliveries\" >= 0", + "name": "integration_connection_exhausted_count_check", + "entityType": "checks", + "schema": "public", + "table": "integration_connection" + }, + { + "value": "\"attempt_number\" > 0", + "name": "integration_delivery_attempt_number_check", + "entityType": "checks", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "value": "\"duration_ms\" IS NULL OR \"duration_ms\" >= 0", + "name": "integration_delivery_attempt_duration_check", + "entityType": "checks", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "value": "\"http_status\" IS NULL OR \"http_status\" BETWEEN 100 AND 599", + "name": "integration_delivery_attempt_http_status_check", + "entityType": "checks", + "schema": "public", + "table": "integration_delivery_attempt" + }, + { + "value": "\"attempt_count\" >= 0", + "name": "integration_delivery_attempt_count_check", + "entityType": "checks", + "schema": "public", + "table": "integration_delivery" + }, + { + "value": "(\"state\" = 'leased') = (\"lease_owner\" IS NOT NULL AND \"lease_expires_at\" IS NOT NULL)", + "name": "integration_delivery_lease_state_check", + "entityType": "checks", + "schema": "public", + "table": "integration_delivery" + }, + { + "value": "(\"state\" = 'succeeded') = (\"succeeded_at\" IS NOT NULL) AND (\"state\" = 'exhausted') = (\"exhausted_at\" IS NOT NULL) AND (\"state\" = 'canceled') = (\"canceled_at\" IS NOT NULL)", + "name": "integration_delivery_terminal_timestamp_check", + "entityType": "checks", + "schema": "public", + "table": "integration_delivery" + }, + { + "value": "\"version\" > 0", + "name": "integration_event_version_check", + "entityType": "checks", + "schema": "public", + "table": "integration_event" + }, + { + "value": "\"causal_hop_count\" >= 0", + "name": "integration_event_causal_hop_count_check", + "entityType": "checks", + "schema": "public", + "table": "integration_event" + }, + { + "value": "\"config_version\" > 0", + "name": "integration_route_config_version_check", + "entityType": "checks", + "schema": "public", + "table": "integration_route" + }, + { + "value": "(\"user_id\" IS NOT NULL) <> (\"organization_id\" IS NOT NULL)", + "name": "asset_owner_check", + "entityType": "checks", + "schema": "public", + "table": "asset" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/db/src/relations.ts b/packages/db/src/relations.ts index ab0176df..23927b9e 100644 --- a/packages/db/src/relations.ts +++ b/packages/db/src/relations.ts @@ -22,10 +22,15 @@ import { emailOutboxTable, emailProviderEventTable, emailSubscriptionTable, + externalResourceCreateRequestTable, + githubInstallationTable, + githubSyncRuleTable, + githubWebhookDeliveryTable, integrationConnectionTable, integrationDeliveryAttemptTable, integrationDeliveryTable, integrationEventTable, + integrationExternalResourceTable, integrationRouteTable, invitationTable, jwtSecretTable, @@ -33,6 +38,7 @@ import { organizationTable, postActivityTable, postAssetTable, + postExternalResourceLinkTable, postReactionTable, postStatusTable, postSubscriptionTable, @@ -66,10 +72,16 @@ export const relations = defineRelations( memberTable, invitationTable, integrationConnectionTable, + githubInstallationTable, integrationRouteTable, integrationEventTable, integrationDeliveryTable, integrationDeliveryAttemptTable, + integrationExternalResourceTable, + postExternalResourceLinkTable, + githubSyncRuleTable, + githubWebhookDeliveryTable, + externalResourceCreateRequestTable, subscriptionTable, productTable, boardTable, @@ -845,6 +857,16 @@ export const relations = defineRelations( from: r.integrationConnectionTable.id, to: r.integrationDeliveryTable.connectionId, }), + githubInstallation: r.one.githubInstallationTable({ + from: r.integrationConnectionTable.id, + to: r.githubInstallationTable.connectionId, + }), + }, + githubInstallationTable: { + connection: r.one.integrationConnectionTable({ + from: r.githubInstallationTable.connectionId, + to: r.integrationConnectionTable.id, + }), }, integrationRouteTable: { organization: r.one.organizationTable({ @@ -898,5 +920,41 @@ export const relations = defineRelations( to: r.integrationDeliveryTable.id, }), }, + integrationExternalResourceTable: { + connection: r.one.integrationConnectionTable({ + from: r.integrationExternalResourceTable.connectionId, + to: r.integrationConnectionTable.id, + }), + links: r.many.postExternalResourceLinkTable({ + from: r.integrationExternalResourceTable.id, + to: r.postExternalResourceLinkTable.externalResourceId, + }), + }, + postExternalResourceLinkTable: { + post: r.one.postTable({ + from: r.postExternalResourceLinkTable.postId, + to: r.postTable.id, + }), + externalResource: r.one.integrationExternalResourceTable({ + from: r.postExternalResourceLinkTable.externalResourceId, + to: r.integrationExternalResourceTable.id, + }), + }, + githubSyncRuleTable: { + connection: r.one.integrationConnectionTable({ + from: r.githubSyncRuleTable.connectionId, + to: r.integrationConnectionTable.id, + }), + postStatus: r.one.postStatusTable({ + from: r.githubSyncRuleTable.postStatusId, + to: r.postStatusTable.id, + }), + }, + githubWebhookDeliveryTable: { + connection: r.one.integrationConnectionTable({ + from: r.githubWebhookDeliveryTable.connectionId, + to: r.integrationConnectionTable.id, + }), + }, }) ); diff --git a/packages/db/src/schema/feedback.ts b/packages/db/src/schema/feedback.ts index 6f297d4b..f74ac074 100644 --- a/packages/db/src/schema/feedback.ts +++ b/packages/db/src/schema/feedback.ts @@ -208,6 +208,10 @@ export const postStatusTable = pgTable( }, (table) => [ index("post_status_organizationId_idx").on(table.organizationId), + uniqueIndex("post_status_organizationId_id_uidx").on( + table.organizationId, + table.id + ), uniqueIndex("post_status_organizationId_type_uidx").on( table.organizationId, table.type diff --git a/packages/db/src/schema/integration.ts b/packages/db/src/schema/integration.ts index 14b10be4..6b3bb813 100644 --- a/packages/db/src/schema/integration.ts +++ b/packages/db/src/schema/integration.ts @@ -12,6 +12,13 @@ import { uniqueIndex, } from "drizzle-orm/pg-core"; import type { + TGitHubInstallationAccountType, + TGitHubIssueMatchMode, + TGitHubIssueState, + TGitHubUpvoterNotificationPolicy, +} from "../validation-schema/github-integration"; +import type { + TExternalResourceCreateRequestState, TIntegrationCapabilityKey, TIntegrationConnectionLifecycleStatus, TIntegrationDeliveryAttemptDiagnostics, @@ -19,6 +26,7 @@ import type { TIntegrationDeliveryRetryDecision, TIntegrationDeliveryState, TIntegrationEventType, + TIntegrationExternalResourceType, TIntegrationProviderConfiguration, TIntegrationProviderKey, TIntegrationRouteEventSelection, @@ -27,6 +35,7 @@ import type { TStoredIntegrationEventPayload, } from "../validation-schema/integration"; import { organizationTable } from "./auth"; +import { postStatusTable, postTable } from "./feedback"; /** Durable organization-owned provider connection with credentials stored separately from safe metadata. */ export const integrationConnectionTable = pgTable( @@ -96,6 +105,38 @@ export const integrationConnectionTable = pgTable( ] ); +/** GitHub App installation identity bound one-to-one with an integration connection. */ +export const githubInstallationTable = pgTable( + "github_installation", + { + connectionId: text("connection_id") + .primaryKey() + .references(() => integrationConnectionTable.id, { onDelete: "cascade" }), + /** GitHub's durable installation identifier; installation access tokens are never stored. */ + installationId: text("installation_id").notNull(), + accountId: text("account_id").notNull(), + accountLogin: text("account_login").notNull(), + accountType: text("account_type") + .$type() + .notNull(), + /** Present while GitHub has suspended the App installation. */ + suspendedAt: timestamp("suspended_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [ + uniqueIndex("github_installation_installation_id_uidx").on( + table.installationId + ), + index("github_installation_account_idx").on(table.accountId), + ] +); + /** Provider-owned capability configuration and subscribable event selection. */ export const integrationRouteTable = pgTable( "integration_route", @@ -154,6 +195,10 @@ export const integrationRouteTable = pgTable( table.organizationId, table.id ), + uniqueIndex("integration_route_connection_id_uidx").on( + table.connectionId, + table.id + ), // One route per (connection, capability, routeKey). Capabilities with a // single route per connection (webhook events.post, Slack inbound) use an // empty routeKey; providers with multiple routes per capability (Slack @@ -275,6 +320,14 @@ export const integrationDeliveryTable = pgTable( ], name: "integration_delivery_organization_route_fkey", }).onDelete("cascade"), + foreignKey({ + columns: [table.connectionId, table.routeId], + foreignColumns: [ + integrationRouteTable.connectionId, + integrationRouteTable.id, + ], + name: "integration_delivery_connection_route_fkey", + }).onDelete("cascade"), foreignKey({ columns: [table.organizationId, table.eventId], foreignColumns: [ @@ -358,3 +411,281 @@ export const integrationDeliveryAttemptTable = pgTable( ), ] ); + +/** One provider-owned resource which can be linked to many Feeblo posts. */ +export const integrationExternalResourceTable = pgTable( + "integration_external_resource", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organizationTable.id, { onDelete: "cascade" }), + connectionId: text("connection_id").notNull(), + resourceType: text("resource_type") + .$type() + .notNull(), + remoteId: text("remote_id").notNull(), + remoteUrl: text("remote_url").notNull(), + displayKey: text("display_key"), + title: text("title"), + stateKey: text("state_key"), + safeMetadata: jsonb("safe_metadata") + .$type() + .notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [ + uniqueIndex("integration_external_resource_organization_id_uidx").on( + table.organizationId, + table.id + ), + foreignKey({ + columns: [table.organizationId, table.connectionId], + foreignColumns: [ + integrationConnectionTable.organizationId, + integrationConnectionTable.id, + ], + name: "integration_external_resource_organization_connection_fkey", + }).onDelete("cascade"), + uniqueIndex("integration_external_resource_connection_type_remote_uidx").on( + table.connectionId, + table.resourceType, + table.remoteId + ), + index("integration_external_resource_organization_connection_idx").on( + table.organizationId, + table.connectionId + ), + ] +); + +/** Shared SQL conditions matching a GitHub issue external resource by its safe-metadata identity. */ +export const gitHubIssueSafeMetadataConditions = ({ + issueNumber, + repositoryName, + repositoryOwner, +}: { + readonly issueNumber: number; + readonly repositoryName: string; + readonly repositoryOwner: string; +}) => [ + sql`${integrationExternalResourceTable.safeMetadata}->>'repositoryOwner' = ${repositoryOwner}`, + sql`${integrationExternalResourceTable.safeMetadata}->>'repositoryName' = ${repositoryName}`, + sql`(${integrationExternalResourceTable.safeMetadata}->>'issueNumber')::integer = ${issueNumber}`, +]; + +/** A normalized many-to-many link from a Feeblo post to an external resource. */ +export const postExternalResourceLinkTable = pgTable( + "post_external_resource_link", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organizationTable.id, { onDelete: "cascade" }), + postId: text("post_id").notNull(), + externalResourceId: text("external_resource_id").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [ + uniqueIndex("post_external_resource_link_organization_id_uidx").on( + table.organizationId, + table.id + ), + foreignKey({ + columns: [table.postId, table.organizationId], + foreignColumns: [postTable.id, postTable.organizationId], + name: "post_external_resource_link_post_organization_fkey", + }).onDelete("cascade"), + foreignKey({ + columns: [table.organizationId, table.externalResourceId], + foreignColumns: [ + integrationExternalResourceTable.organizationId, + integrationExternalResourceTable.id, + ], + name: "post_external_resource_link_organization_resource_fkey", + }).onDelete("cascade"), + uniqueIndex("post_external_resource_link_post_resource_uidx").on( + table.postId, + table.externalResourceId + ), + index("post_external_resource_link_organization_post_idx").on( + table.organizationId, + table.postId + ), + ] +); + +/** Organization-owned rule mapping aggregate linked GitHub issue state to a Feeblo status. Only the (any, open) and (all, closed) shapes exist, at most one per connection. */ +export const githubSyncRuleTable = pgTable( + "github_sync_rule", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organizationTable.id, { onDelete: "cascade" }), + connectionId: text("connection_id").notNull(), + issueMatchMode: text("issue_match_mode") + .$type() + .notNull(), + issueState: text("issue_state").$type().notNull(), + postStatusId: text("post_status_id").notNull(), + upvoterNotificationPolicy: text("upvoter_notification_policy") + .$type() + .notNull(), + enabled: boolean("enabled").default(true).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId, table.connectionId], + foreignColumns: [ + integrationConnectionTable.organizationId, + integrationConnectionTable.id, + ], + name: "github_sync_rule_organization_connection_fkey", + }).onDelete("cascade"), + foreignKey({ + columns: [table.organizationId, table.postStatusId], + foreignColumns: [postStatusTable.organizationId, postStatusTable.id], + name: "github_sync_rule_organization_status_fkey", + }).onDelete("cascade"), + // Only the hard-wired (any, open) and (all, closed) shapes are valid; they + // can never match the same issue aggregate, keeping rule application + // deterministic without an in-app conflict check. + check( + "github_sync_rule_combo_ck", + sql`(${table.issueMatchMode} = 'any' AND ${table.issueState} = 'open') OR (${table.issueMatchMode} = 'all' AND ${table.issueState} = 'closed')` + ), + // Each connection owns at most one rule per shape. + uniqueIndex("github_sync_rule_open_connection_uq") + .on(table.connectionId) + .where( + sql`${table.issueMatchMode} = 'any' AND ${table.issueState} = 'open'` + ), + uniqueIndex("github_sync_rule_closed_connection_uq") + .on(table.connectionId) + .where( + sql`${table.issueMatchMode} = 'all' AND ${table.issueState} = 'closed'` + ), + index("github_sync_rule_connection_enabled_idx").on( + table.connectionId, + table.enabled + ), + index("github_sync_rule_organization_idx").on(table.organizationId), + ] +); + +/** Durable inbox record preventing a redelivered GitHub webhook from applying twice. */ +export const githubWebhookDeliveryTable = pgTable( + "github_webhook_delivery", + { + id: text("id").primaryKey(), + connectionId: text("connection_id") + .notNull() + .references(() => integrationConnectionTable.id, { onDelete: "cascade" }), + deliveryId: text("delivery_id").notNull(), + eventName: text("event_name").notNull(), + receivedAt: timestamp("received_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [ + uniqueIndex("github_webhook_delivery_connection_delivery_uidx").on( + table.connectionId, + table.deliveryId + ), + ] +); + +/** Idempotency reservation for one user-requested external-resource creation; external I/O occurs after pending is committed. */ +export const externalResourceCreateRequestTable = pgTable( + "external_resource_create_request", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organizationTable.id, { onDelete: "cascade" }), + connectionId: text("connection_id").notNull(), + postId: text("post_id").notNull(), + idempotencyKey: text("idempotency_key").notNull(), + state: text("state").$type().notNull(), + externalResourceId: text("external_resource_id"), + postExternalResourceLinkId: text("post_external_resource_link_id"), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId, table.connectionId], + foreignColumns: [ + integrationConnectionTable.organizationId, + integrationConnectionTable.id, + ], + name: "external_resource_create_request_organization_connection_fkey", + }).onDelete("cascade"), + foreignKey({ + columns: [table.postId, table.organizationId], + foreignColumns: [postTable.id, postTable.organizationId], + name: "external_resource_create_request_post_organization_fkey", + }).onDelete("cascade"), + // Column-specific ON DELETE SET NULL so deleting the referenced resource or + // link nulls only the nullable id column and never the NOT NULL + // organization_id (a composite SET NULL would try to null both and fail). + // Declared before the composite integrity FKs below so the generated DDL + // creates them first: Postgres fires FK triggers in constraint creation + // order, so the SET NULL action must run before the composite NO ACTION + // check. + foreignKey({ + columns: [table.externalResourceId], + foreignColumns: [integrationExternalResourceTable.id], + name: "external_resource_create_request_resource_fkey", + }).onDelete("set null"), + foreignKey({ + columns: [table.postExternalResourceLinkId], + foreignColumns: [postExternalResourceLinkTable.id], + name: "external_resource_create_request_link_fkey", + }).onDelete("set null"), + // Composite (organization_id, id) integrity constraints keep resources and + // links organization-scoped. NO ACTION (default) leaves the delete behavior + // to the column-specific SET NULL constraints above. + foreignKey({ + columns: [table.organizationId, table.externalResourceId], + foreignColumns: [ + integrationExternalResourceTable.organizationId, + integrationExternalResourceTable.id, + ], + name: "external_resource_create_request_organization_resource_fkey", + }), + foreignKey({ + columns: [table.organizationId, table.postExternalResourceLinkId], + foreignColumns: [ + postExternalResourceLinkTable.organizationId, + postExternalResourceLinkTable.id, + ], + name: "external_resource_create_request_organization_link_fkey", + }), + uniqueIndex("external_resource_create_request_connection_key_uidx").on( + table.connectionId, + table.idempotencyKey + ), + ] +); diff --git a/packages/db/src/validation-schema/github-integration.ts b/packages/db/src/validation-schema/github-integration.ts new file mode 100644 index 00000000..c97f03c7 --- /dev/null +++ b/packages/db/src/validation-schema/github-integration.ts @@ -0,0 +1,48 @@ +import * as S from "effect/Schema"; + +/** Account kinds GitHub can own an App installation for. */ +export const GitHubInstallationAccountType = S.Literals([ + "User", + "Organization", +]); +export type TGitHubInstallationAccountType = S.Schema.Type< + typeof GitHubInstallationAccountType +>; + +/** GitHub issue state accepted from GitHub webhook payloads. */ +export const GitHubIssueState = S.Literals(["open", "closed"]); +export type TGitHubIssueState = S.Schema.Type; + +/** Selects whether every or at least one linked issue must match a rule. */ +export const GitHubIssueMatchMode = S.Literals(["all", "any"]); +export type TGitHubIssueMatchMode = S.Schema.Type; + +/** Scope used by an automatic GitHub issue publishing route. */ +export const GitHubPublishBoardScope = S.Literals([ + "any_board", + "specific_board", +]); +export type TGitHubPublishBoardScope = S.Schema.Type< + typeof GitHubPublishBoardScope +>; + +/** Notification policy for a status transition caused by a GitHub issue. */ +export const GitHubUpvoterNotificationPolicy = S.Literals([ + "notify_upvoters", + "do_not_notify_upvoters", +]); +export type TGitHubUpvoterNotificationPolicy = S.Schema.Type< + typeof GitHubUpvoterNotificationPolicy +>; + +/** + * The two supported GitHub sync rule shapes: (any, open) fires when any linked + * issue is open, (all, closed) fires only when every linked issue is closed. + * They can never match the same issue aggregate, so at most one rule applies. + */ +export const isGitHubSyncRuleCombination = ( + issueMatchMode: TGitHubIssueMatchMode, + issueState: TGitHubIssueState +): boolean => + (issueMatchMode === "any" && issueState === "open") || + (issueMatchMode === "all" && issueState === "closed"); diff --git a/packages/db/src/validation-schema/integration.ts b/packages/db/src/validation-schema/integration.ts index ef21e4b4..7d22d4ab 100644 --- a/packages/db/src/validation-schema/integration.ts +++ b/packages/db/src/validation-schema/integration.ts @@ -8,6 +8,24 @@ export type TIntegrationProviderKey = S.Schema.Type< typeof IntegrationProviderKey >; +/** Provider-owned external resource kind, such as `issue`, `task`, or `ticket`. */ +export const IntegrationExternalResourceType = S.NonEmptyString.pipe( + S.brand("IntegrationExternalResourceType") +); +export type TIntegrationExternalResourceType = S.Schema.Type< + typeof IntegrationExternalResourceType +>; + +/** Durable external resource creation lifecycle. */ +export const ExternalResourceCreateRequestState = S.Literals([ + "pending", + "succeeded", + "failed", +]); +export type TExternalResourceCreateRequestState = S.Schema.Type< + typeof ExternalResourceCreateRequestState +>; + /** Canonical provider capability directions used by static manifests. */ export const IntegrationCapabilityDirection = S.Literals([ "outbound", @@ -23,19 +41,16 @@ export const IntegrationConnectionMode = S.Literals([ "none", "oauth2", "api_key", + "github_app", ]); export type TIntegrationConnectionMode = S.Schema.Type< typeof IntegrationConnectionMode >; -/** Capabilities persisted by integration routes. `events.post` is the V1 custom-webhook capability; Slack and Discord own the remaining keys. */ -export const IntegrationCapabilityKey = S.Literals([ - "events.post", - "channel.notifications", - "commands", - "message.action", - "interactions", -]); +/** Provider capability key persisted by integration routes; providers own their keys and the startup registry validates them. */ +export const IntegrationCapabilityKey = S.NonEmptyString.pipe( + S.brand("IntegrationCapabilityKey") +); export type TIntegrationCapabilityKey = S.Schema.Type< typeof IntegrationCapabilityKey >; diff --git a/packages/domain/package.json b/packages/domain/package.json index c67c4837..0e8afd0a 100644 --- a/packages/domain/package.json +++ b/packages/domain/package.json @@ -34,6 +34,9 @@ "./notifications/batches": { "default": "./src/notifications/batches.ts" }, + "./notification/service": { + "default": "./src/notification/service.ts" + }, "./widget/schema": { "default": "./src/widget/schema.ts" }, @@ -283,6 +286,21 @@ "./integration/webhook-management-service": { "default": "./src/integration/webhook-management-service.ts" }, + "./integration/external-resource/schema": { + "default": "./src/integration/external-resource/schema.ts" + }, + "./integration/external-resource/service": { + "default": "./src/integration/external-resource/service.ts" + }, + "./integration/external-resource/live": { + "default": "./src/integration/external-resource/live.ts" + }, + "./integration/external-resource/rpcs": { + "default": "./src/integration/external-resource/rpcs.ts" + }, + "./integration/external-resource/handlers": { + "default": "./src/integration/external-resource/handlers.ts" + }, "./email-outbox/repository": { "default": "./src/email-outbox/repository.ts" }, @@ -358,6 +376,33 @@ "./integration/discord/handlers": { "default": "./src/integration/discord/handlers.ts" }, + "./integration/github": { + "default": "./src/integration/github/index.ts" + }, + "./integration/github/config": { + "default": "./src/integration/github/config.ts" + }, + "./integration/github/github-provider": { + "default": "./src/integration/github/github-provider.ts" + }, + "./integration/github/inbound-service": { + "default": "./src/integration/github/inbound-service.ts" + }, + "./integration/github/inbound-live": { + "default": "./src/integration/github/inbound-live.ts" + }, + "./integration/github/management-live": { + "default": "./src/integration/github/management-live.ts" + }, + "./integration/github/management-service": { + "default": "./src/integration/github/management-service.ts" + }, + "./integration/github/oauth-callback": { + "default": "./src/integration/github/oauth-callback.ts" + }, + "./integration/github/schema": { + "default": "./src/integration/github/schema.ts" + }, "./board/repository": { "default": "./src/board/repository.ts" }, @@ -374,11 +419,12 @@ "dependencies": { "@effect-aws/client-s3": "catalog:", "@effect-aws/s3": "catalog:", - "@effect/ai-openai": "4.0.0-beta.66", + "@effect/ai-openai": "catalog:", "@feeblo/db": "workspace:*", "@feeblo/id": "workspace:*", "@feeblo/integration-core": "workspace:*", "@feeblo/integration-discord": "workspace:*", + "@feeblo/integration-github": "workspace:*", "@feeblo/integration-slack": "workspace:*", "@feeblo/integration-webhook": "workspace:*", "@feeblo/permissions": "workspace:*", @@ -401,8 +447,7 @@ "test:watch": "vitest" }, "devDependencies": { - "@effect/platform-node": "catalog:", - "@effect/vitest": "4.0.0-beta.94", + "@effect/vitest": "catalog:", "@feeblo/config": "workspace:*", "@types/react": "catalog:", "typescript": "catalog:", diff --git a/packages/domain/src/attribute-definition/errors.ts b/packages/domain/src/attribute-definition/errors.ts index b2f51d6f..3007cff8 100644 --- a/packages/domain/src/attribute-definition/errors.ts +++ b/packages/domain/src/attribute-definition/errors.ts @@ -7,13 +7,13 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class AttributeDefinitionNotFoundError extends Schema.TaggedErrorClass()( +export class AttributeDefinitionNotFoundError extends Schema.TaggedError()( "AttributeDefinitionNotFoundError", { message: Schema.optional(Schema.String) }, { httpApiStatus: 404, identifier: "AttributeDefinitionNotFoundError" } ) {} -export class FailedToUpsertAttributeValueError extends Schema.TaggedErrorClass()( +export class FailedToUpsertAttributeValueError extends Schema.TaggedError()( "FailedToUpsertAttributeValueError", {}, { httpApiStatus: 500, identifier: "FailedToUpsertAttributeValueError" } diff --git a/packages/domain/src/auth/utils.ts b/packages/domain/src/auth/utils.ts index c27e1201..b31ad86e 100644 --- a/packages/domain/src/auth/utils.ts +++ b/packages/domain/src/auth/utils.ts @@ -16,21 +16,21 @@ const VerificationOTPStateFromJson = Schema.fromJsonString( VerificationOTPStateSchema ); -class VerificationOTPEncryptionError extends Schema.TaggedErrorClass()( +class VerificationOTPEncryptionError extends Schema.TaggedError()( "VerificationOTPEncryptionError", { cause: Schema.Defect(), } ) {} -class VerificationOTPDecryptionError extends Schema.TaggedErrorClass()( +class VerificationOTPDecryptionError extends Schema.TaggedError()( "VerificationOTPDecryptionError", { cause: Schema.Defect(), } ) {} -class InvalidVerificationOTPStateError extends Schema.TaggedErrorClass()( +class InvalidVerificationOTPStateError extends Schema.TaggedError()( "InvalidVerificationOTPStateError", { cause: Schema.optional(Schema.Defect()), diff --git a/packages/domain/src/billing/errors.ts b/packages/domain/src/billing/errors.ts index 1fe9257a..9a803acb 100644 --- a/packages/domain/src/billing/errors.ts +++ b/packages/domain/src/billing/errors.ts @@ -7,7 +7,7 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class FailedToCreateCheckoutError extends Schema.TaggedErrorClass()( +export class FailedToCreateCheckoutError extends Schema.TaggedError()( "FailedToCreateCheckoutError", { message: Schema.optional(Schema.String), @@ -15,7 +15,7 @@ export class FailedToCreateCheckoutError extends Schema.TaggedErrorClass()( +export class FailedToCreatePortalError extends Schema.TaggedError()( "FailedToCreatePortalError", { message: Schema.optional(Schema.String), @@ -28,7 +28,7 @@ export class FailedToCreatePortalError extends Schema.TaggedErrorClass()( +export class FailedToRevokeSubscriptionError extends Schema.TaggedError()( "FailedToRevokeSubscriptionError", { message: Schema.optional(Schema.String), diff --git a/packages/domain/src/board/errors.ts b/packages/domain/src/board/errors.ts index c5a5813c..934da224 100644 --- a/packages/domain/src/board/errors.ts +++ b/packages/domain/src/board/errors.ts @@ -7,7 +7,7 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class BoardNotFoundError extends Schema.TaggedErrorClass()( +export class BoardNotFoundError extends Schema.TaggedError()( "BoardNotFoundError", { message: Schema.optional(Schema.String), @@ -15,19 +15,19 @@ export class BoardNotFoundError extends Schema.TaggedErrorClass()( +export class FailedToCreateBoardError extends Schema.TaggedError()( "FailedToCreateBoardError", {}, { httpApiStatus: 500, identifier: "FailedToCreateBoardError" } ) {} -export class FailedToUpdateBoardError extends Schema.TaggedErrorClass()( +export class FailedToUpdateBoardError extends Schema.TaggedError()( "FailedToUpdateBoardError", {}, { httpApiStatus: 500, identifier: "FailedToUpdateBoardError" } ) {} -export class FailedToDeleteBoardError extends Schema.TaggedErrorClass()( +export class FailedToDeleteBoardError extends Schema.TaggedError()( "FailedToDeleteBoardError", {}, { httpApiStatus: 500, identifier: "FailedToDeleteBoardError" } diff --git a/packages/domain/src/changelog-category/errors.ts b/packages/domain/src/changelog-category/errors.ts index dfe86bc5..14f6e061 100644 --- a/packages/domain/src/changelog-category/errors.ts +++ b/packages/domain/src/changelog-category/errors.ts @@ -7,19 +7,19 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class FailedToCreateChangelogCategoryError extends Schema.TaggedErrorClass()( +export class FailedToCreateChangelogCategoryError extends Schema.TaggedError()( "FailedToCreateChangelogCategoryError", {}, { httpApiStatus: 500, identifier: "FailedToCreateChangelogCategoryError" } ) {} -export class FailedToUpdateChangelogCategoryError extends Schema.TaggedErrorClass()( +export class FailedToUpdateChangelogCategoryError extends Schema.TaggedError()( "FailedToUpdateChangelogCategoryError", {}, { httpApiStatus: 500, identifier: "FailedToUpdateChangelogCategoryError" } ) {} -export class FailedToDeleteChangelogCategoryError extends Schema.TaggedErrorClass()( +export class FailedToDeleteChangelogCategoryError extends Schema.TaggedError()( "FailedToDeleteChangelogCategoryError", {}, { httpApiStatus: 500, identifier: "FailedToDeleteChangelogCategoryError" } diff --git a/packages/domain/src/changelog/errors.ts b/packages/domain/src/changelog/errors.ts index f779cc70..e09b6c85 100644 --- a/packages/domain/src/changelog/errors.ts +++ b/packages/domain/src/changelog/errors.ts @@ -3,19 +3,19 @@ import * as Schema from "effect/Schema"; import { PolicyDeniedError } from "../policy"; import { InternalServerError, UnauthorizedError } from "../rpc-errors"; -export class FailedToCreateChangelogError extends Schema.TaggedErrorClass()( +export class FailedToCreateChangelogError extends Schema.TaggedError()( "FailedToCreateChangelogError", {}, { httpApiStatus: 500, identifier: "FailedToCreateChangelogError" } ) {} -export class FailedToDeleteChangelogError extends Schema.TaggedErrorClass()( +export class FailedToDeleteChangelogError extends Schema.TaggedError()( "FailedToDeleteChangelogError", {}, { httpApiStatus: 500, identifier: "FailedToDeleteChangelogError" } ) {} -export class FailedToUpdateChangelogError extends Schema.TaggedErrorClass()( +export class FailedToUpdateChangelogError extends Schema.TaggedError()( "FailedToUpdateChangelogError", {}, { httpApiStatus: 500, identifier: "FailedToUpdateChangelogError" } diff --git a/packages/domain/src/comments/errors.ts b/packages/domain/src/comments/errors.ts index 6911dd6c..635814bf 100644 --- a/packages/domain/src/comments/errors.ts +++ b/packages/domain/src/comments/errors.ts @@ -3,7 +3,7 @@ import * as Schema from "effect/Schema"; import { PolicyDeniedError } from "../policy"; import { InternalServerError, UnauthorizedError } from "../rpc-errors"; -export class FailedToDeleteCommentError extends Schema.TaggedErrorClass()( +export class FailedToDeleteCommentError extends Schema.TaggedError()( "FailedToDeleteCommentError", { message: Schema.optional(Schema.String), @@ -11,7 +11,7 @@ export class FailedToDeleteCommentError extends Schema.TaggedErrorClass()( +export class FailedToUpdateCommentError extends Schema.TaggedError()( "FailedToUpdateCommentError", { message: Schema.optional(Schema.String), @@ -19,7 +19,7 @@ export class FailedToUpdateCommentError extends Schema.TaggedErrorClass()( +export class FailedToCreateCommentError extends Schema.TaggedError()( "FailedToCreateCommentError", { message: Schema.optional(Schema.String), diff --git a/packages/domain/src/company/errors.ts b/packages/domain/src/company/errors.ts index 79e8ed9b..3ffe494d 100644 --- a/packages/domain/src/company/errors.ts +++ b/packages/domain/src/company/errors.ts @@ -7,31 +7,31 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class CompanyNotFoundError extends Schema.TaggedErrorClass()( +export class CompanyNotFoundError extends Schema.TaggedError()( "CompanyNotFoundError", { message: Schema.optional(Schema.String) }, { httpApiStatus: 404, identifier: "CompanyNotFoundError" } ) {} -export class CompanyAlreadyExistsError extends Schema.TaggedErrorClass()( +export class CompanyAlreadyExistsError extends Schema.TaggedError()( "CompanyAlreadyExistsError", { message: Schema.optional(Schema.String) }, { httpApiStatus: 409, identifier: "CompanyAlreadyExistsError" } ) {} -export class FailedToCreateCompanyError extends Schema.TaggedErrorClass()( +export class FailedToCreateCompanyError extends Schema.TaggedError()( "FailedToCreateCompanyError", {}, { httpApiStatus: 500, identifier: "FailedToCreateCompanyError" } ) {} -export class FailedToUpdateCompanyError extends Schema.TaggedErrorClass()( +export class FailedToUpdateCompanyError extends Schema.TaggedError()( "FailedToUpdateCompanyError", {}, { httpApiStatus: 500, identifier: "FailedToUpdateCompanyError" } ) {} -export class FailedToDeleteCompanyError extends Schema.TaggedErrorClass()( +export class FailedToDeleteCompanyError extends Schema.TaggedError()( "FailedToDeleteCompanyError", {}, { httpApiStatus: 500, identifier: "FailedToDeleteCompanyError" } diff --git a/packages/domain/src/contact/errors.ts b/packages/domain/src/contact/errors.ts index 902eb256..d0b772ae 100644 --- a/packages/domain/src/contact/errors.ts +++ b/packages/domain/src/contact/errors.ts @@ -7,7 +7,7 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class DataValidationError extends Schema.TaggedErrorClass()( +export class DataValidationError extends Schema.TaggedError()( "DataValidationError", { message: Schema.optional(Schema.String), @@ -15,7 +15,7 @@ export class DataValidationError extends Schema.TaggedErrorClass()( +export class ContactNotFoundError extends Schema.TaggedError()( "ContactNotFoundError", { message: Schema.optional(Schema.String), @@ -23,25 +23,25 @@ export class ContactNotFoundError extends Schema.TaggedErrorClass()( +export class ContactAlreadyExistsError extends Schema.TaggedError()( "ContactAlreadyExistsError", { message: Schema.optional(Schema.String) }, { httpApiStatus: 409, identifier: "ContactAlreadyExistsError" } ) {} -export class FailedToCreateContactError extends Schema.TaggedErrorClass()( +export class FailedToCreateContactError extends Schema.TaggedError()( "FailedToCreateContactError", {}, { httpApiStatus: 500, identifier: "FailedToCreateContactError" } ) {} -export class FailedToUpdateContactError extends Schema.TaggedErrorClass()( +export class FailedToUpdateContactError extends Schema.TaggedError()( "FailedToUpdateContactError", {}, { httpApiStatus: 500, identifier: "FailedToUpdateContactError" } ) {} -export class FailedToDeleteContactError extends Schema.TaggedErrorClass()( +export class FailedToDeleteContactError extends Schema.TaggedError()( "FailedToDeleteContactError", {}, { httpApiStatus: 500, identifier: "FailedToDeleteContactError" } diff --git a/packages/domain/src/email-outbox/operations.ts b/packages/domain/src/email-outbox/operations.ts index 687d9575..7094848a 100644 --- a/packages/domain/src/email-outbox/operations.ts +++ b/packages/domain/src/email-outbox/operations.ts @@ -15,7 +15,7 @@ import * as Schema from "effect/Schema"; const PersistedDate = Schema.Union([Schema.Date, Schema.DateFromString]); -export class EmailOutboxInspectionError extends Schema.TaggedErrorClass()( +export class EmailOutboxInspectionError extends Schema.TaggedError()( "EmailOutboxInspectionError", { reason: Schema.String } ) {} diff --git a/packages/domain/src/email-outbox/repository.ts b/packages/domain/src/email-outbox/repository.ts index 02ef5586..cfe1bf7f 100644 --- a/packages/domain/src/email-outbox/repository.ts +++ b/packages/domain/src/email-outbox/repository.ts @@ -26,7 +26,7 @@ import { recordEmailReconciliationRecoveries, } from "./telemetry"; -export class EmailOutboxDataError extends Schema.TaggedErrorClass()( +export class EmailOutboxDataError extends Schema.TaggedError()( "EmailOutboxDataError", { operation: Schema.String, diff --git a/packages/domain/src/email-provider-feedback/schema.ts b/packages/domain/src/email-provider-feedback/schema.ts index 521d9846..7ef657c0 100644 --- a/packages/domain/src/email-provider-feedback/schema.ts +++ b/packages/domain/src/email-provider-feedback/schema.ts @@ -51,7 +51,7 @@ export type ProviderLifecycleEvent = Schema.Schema.Type< typeof ProviderLifecycleEvent >; -export class EmailProviderFeedbackInputError extends Schema.TaggedErrorClass()( +export class EmailProviderFeedbackInputError extends Schema.TaggedError()( "EmailProviderFeedbackInputError", { message: Schema.String, @@ -60,7 +60,7 @@ export class EmailProviderFeedbackInputError extends Schema.TaggedErrorClass()( +export class EmailProviderFeedbackDataError extends Schema.TaggedError()( "EmailProviderFeedbackDataError", { cause: Schema.optionalKey(Schema.Defect()), diff --git a/packages/domain/src/email-subscription/schema.ts b/packages/domain/src/email-subscription/schema.ts index 6c7219db..93e9318e 100644 --- a/packages/domain/src/email-subscription/schema.ts +++ b/packages/domain/src/email-subscription/schema.ts @@ -32,7 +32,7 @@ export const EmailAddress = EmailAddressValue; export type EmailAddress = Schema.Schema.Type; -export class EmailSubscriptionInputError extends Schema.TaggedErrorClass()( +export class EmailSubscriptionInputError extends Schema.TaggedError()( "EmailSubscriptionInputError", { operation: Schema.String, @@ -40,7 +40,7 @@ export class EmailSubscriptionInputError extends Schema.TaggedErrorClass()( +export class EmailSubscriptionDataError extends Schema.TaggedError()( "EmailSubscriptionDataError", { operation: Schema.String, diff --git a/packages/domain/src/email-subscription/tokens.ts b/packages/domain/src/email-subscription/tokens.ts index 9482413c..34e8fbd9 100644 --- a/packages/domain/src/email-subscription/tokens.ts +++ b/packages/domain/src/email-subscription/tokens.ts @@ -6,7 +6,7 @@ import * as Layer from "effect/Layer"; import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; -export class EmailSubscriptionTokenError extends Schema.TaggedErrorClass()( +export class EmailSubscriptionTokenError extends Schema.TaggedError()( "EmailSubscriptionTokenError", { cause: Schema.optionalKey(Schema.Defect()), diff --git a/packages/domain/src/http/upload-limits.ts b/packages/domain/src/http/upload-limits.ts index 34002e4c..6d80cb46 100644 --- a/packages/domain/src/http/upload-limits.ts +++ b/packages/domain/src/http/upload-limits.ts @@ -15,7 +15,7 @@ const MAX_FIELD_BYTES = 1 * 1024 * 1024; * this schema declares them in API contracts so clients and OpenAPI know the * endpoint can respond with 413 Payload Too Large. */ -export class UploadLimitError extends Schema.TaggedErrorClass()( +export class UploadLimitError extends Schema.TaggedError()( "UploadLimitError", { message: Schema.optional(Schema.String), diff --git a/packages/domain/src/integration/discord/discord-channel-service.ts b/packages/domain/src/integration/discord/discord-channel-service.ts index b916c606..f86ca117 100644 --- a/packages/domain/src/integration/discord/discord-channel-service.ts +++ b/packages/domain/src/integration/discord/discord-channel-service.ts @@ -5,7 +5,10 @@ import { type DiscordApiClient, makeDiscordApiClient, } from "@feeblo/integration-discord"; -import { DiscordChannelNotificationRouteConfiguration } from "@feeblo/integration-discord/manifest"; +import { + DiscordChannelNotificationRouteConfiguration, + discordChannelNotificationsCapabilityKey, +} from "@feeblo/integration-discord/manifest"; import { and, eq } from "drizzle-orm"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; @@ -95,7 +98,7 @@ export const makeDiscordChannelServiceLive = ( ), eq( schema.integrationRouteTable.capabilityKey, - "channel.notifications" + discordChannelNotificationsCapabilityKey ), eq(schema.integrationRouteTable.enabled, true) ) @@ -200,7 +203,7 @@ export const makeDiscordChannelServiceLive = ( ), eq( schema.integrationRouteTable.capabilityKey, - "channel.notifications" + discordChannelNotificationsCapabilityKey ), eq( schema.integrationRouteTable.organizationId, @@ -215,7 +218,7 @@ export const makeDiscordChannelServiceLive = ( if (input.enabled) { if (route === undefined) { yield* db.insert(schema.integrationRouteTable).values({ - capabilityKey: "channel.notifications", + capabilityKey: discordChannelNotificationsCapabilityKey, configVersion: 1, connectionId: input.connectionId, enabled: true, diff --git a/packages/domain/src/integration/discord/discord-connection-service.ts b/packages/domain/src/integration/discord/discord-connection-service.ts index 39e312b7..d03d538f 100644 --- a/packages/domain/src/integration/discord/discord-connection-service.ts +++ b/packages/domain/src/integration/discord/discord-connection-service.ts @@ -14,7 +14,10 @@ import { makeDiscordApiClient, } from "@feeblo/integration-discord"; import { encryptDiscordCredentialMaterial } from "@feeblo/integration-discord/credentials"; -import { discordProviderKey } from "@feeblo/integration-discord/manifest"; +import { + discordInteractionsCapabilityKey, + discordProviderKey, +} from "@feeblo/integration-discord/manifest"; import { and, desc, eq, inArray, ne } from "drizzle-orm"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; @@ -99,7 +102,7 @@ export const makeDiscordConnectionServiceLive = ( const config = yield* DiscordIntegrationConfig; const connectStart = Effect.fn("DiscordConnection.connectStart")( - function* ({ organizationId }: { readonly organizationId: string }) { + function* ({ organizationId }: S.TDiscordConnectStart) { if (!config.configured) { return yield* new InternalServerError({ message: "Discord integration is not configured", @@ -393,7 +396,7 @@ export const makeDiscordConnectionServiceLive = ( yield* db .insert(schema.integrationRouteTable) .values({ - capabilityKey: "interactions", + capabilityKey: discordInteractionsCapabilityKey, configVersion: 1, connectionId: connection.id, enabled: true, diff --git a/packages/domain/src/integration/discord/discord-feedback-service.ts b/packages/domain/src/integration/discord/discord-feedback-service.ts index 8ffb219f..ac3e4b5c 100644 --- a/packages/domain/src/integration/discord/discord-feedback-service.ts +++ b/packages/domain/src/integration/discord/discord-feedback-service.ts @@ -142,6 +142,7 @@ export const makeDiscordFeedbackServiceLive = (): Layer.Layer< yield* recordPostIntegrationEvent({ actor: { kind: "end_user" }, boardId: asLegid(BoardId)(boardId), + description: sanitizedMarkdown, eventType: "feedback.post.created", organizationId: asLegid(WorkspaceId)(organizationId), postId: id, diff --git a/packages/domain/src/integration/external-resource/handlers.ts b/packages/domain/src/integration/external-resource/handlers.ts new file mode 100644 index 00000000..934e43b4 --- /dev/null +++ b/packages/domain/src/integration/external-resource/handlers.ts @@ -0,0 +1,25 @@ +import * as Effect from "effect/Effect"; +import * as Policy from "../../policy"; +import { ExternalResourceRpcs } from "./rpcs"; +import { ExternalResourceService } from "./service"; + +/** Authorizes external-resource link reads at the integration-management boundary. */ +export const ExternalResourceRpcHandlersEffect = Effect.gen(function* () { + const service = yield* ExternalResourceService; + return { + PostExternalResourceLinkList: ( + input: Parameters[0] + ) => + service + .listPostLinks(input) + .pipe( + Policy.withPolicy( + Policy.canPermission(input.organizationId, "integrations.manage") + ) + ), + }; +}); + +export const ExternalResourceRpcHandlers = ExternalResourceRpcs.toLayer( + ExternalResourceRpcHandlersEffect +); diff --git a/packages/domain/src/integration/external-resource/live.test.ts b/packages/domain/src/integration/external-resource/live.test.ts new file mode 100644 index 00000000..55599591 --- /dev/null +++ b/packages/domain/src/integration/external-resource/live.test.ts @@ -0,0 +1,212 @@ +import { describe, expect, layer } from "@effect/vitest"; +import { currentDb, Database, schema } from "@feeblo/db"; +import { + IntegrationExternalResourceType, + IntegrationProviderKey, +} from "@feeblo/db/validation-schema/integration"; +import { + BoardId, + IntegrationConnectionId, + type LegidOf, + PostId, + PostStatusId, + WorkspaceId, +} from "@feeblo/id"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import { ExternalResourceServiceLive } from "./live"; +import { ExternalResourceService } from "./service"; + +const TestLayer = Layer.mergeAll( + ExternalResourceServiceLive.pipe(Layer.provide(Database.PgliteDatabaseLive)), + Database.PgliteDatabaseLive +); + +const seedPost = (suffix: string) => + Effect.gen(function* () { + const db = yield* currentDb; + const organizationId = yield* WorkspaceId.generate; + const boardId = yield* BoardId.generate; + const statusId = yield* PostStatusId.generate; + const postId = yield* PostId.generate; + const now = new Date(); + yield* db.insert(schema.organizationTable).values({ + id: organizationId, + name: `External resource ${suffix}`, + slug: `external-resource-${suffix}-${organizationId}`, + createdAt: now, + }); + yield* db.insert(schema.boardTable).values({ + id: boardId, + organizationId, + name: "Feedback", + slug: `feedback-${suffix}`, + visibility: "PRIVATE", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.postStatusTable).values({ + id: statusId, + organizationId, + type: "PENDING", + orderIndex: 0, + }); + yield* db.insert(schema.postTable).values({ + id: postId, + organizationId, + boardId, + statusId, + title: "Feedback", + slug: `feedback-${suffix}`, + content: "Content", + createdAt: now, + updatedAt: now, + }); + return { organizationId, postId, boardId, statusId }; + }); + +const seedPostInOrganization = ( + organizationId: LegidOf<"WorkspaceId">, + boardId: LegidOf<"BoardId">, + statusId: LegidOf<"PostStatusId">, + suffix: string +) => + Effect.gen(function* () { + const db = yield* currentDb; + const postId = yield* PostId.generate; + const now = new Date(); + yield* db.insert(schema.postTable).values({ + id: postId, + organizationId, + boardId, + statusId, + title: "Feedback", + slug: `feedback-${suffix}`, + content: "Content", + createdAt: now, + updatedAt: now, + }); + return { organizationId, postId }; + }); + +const seedConnection = ( + organizationId: LegidOf<"WorkspaceId">, + provider: string +) => + Effect.gen(function* () { + const db = yield* currentDb; + const connectionId = yield* IntegrationConnectionId.generate; + yield* db.insert(schema.integrationConnectionTable).values({ + id: connectionId, + organizationId, + provider: IntegrationProviderKey.make(provider), + name: `${provider} connection`, + lifecycle: "active", + }); + return connectionId; + }); + +const resource = ( + organizationId: LegidOf<"WorkspaceId">, + connectionId: LegidOf<"IntegrationConnectionId"> +) => ({ + organizationId, + connectionId, + resourceType: IntegrationExternalResourceType.make("issue"), + remoteId: "ISSUE-123", + remoteUrl: new URL("https://example.test/issues/ISSUE-123"), + displayKey: "ISSUE-123", + title: "A linked issue", + stateKey: "open", + safeMetadata: {}, +}); + +describe("external resource service", () => { + layer(TestLayer)("generic resource persistence", (it) => { + it.effect( + "lists cross-provider links, upserts duplicate remotes, links one resource to two posts, and reserves creation idempotently", + () => + Effect.gen(function* () { + const service = yield* ExternalResourceService; + const first = yield* seedPost("first"); + const second = yield* seedPostInOrganization( + first.organizationId, + first.boardId, + first.statusId, + "second" + ); + const github = yield* seedConnection(first.organizationId, "github"); + const linear = yield* seedConnection(first.organizationId, "linear"); + + const githubFirst = yield* service.recordPostLink({ + postId: first.postId, + resource: resource(first.organizationId, github), + }); + const githubDuplicate = yield* service.recordPostLink({ + postId: first.postId, + resource: { + ...resource(first.organizationId, github), + title: "Updated title", + }, + }); + expect(githubDuplicate.externalResourceId).toBe( + githubFirst.externalResourceId + ); + expect(githubDuplicate.postExternalResourceLinkId).toBe( + githubFirst.postExternalResourceLinkId + ); + const afterDuplicate = yield* service.listPostLinks(first); + expect(afterDuplicate[0]?.title).toBe("Updated title"); + + yield* service.recordPostLink({ + postId: second.postId, + resource: resource(first.organizationId, github), + }); + yield* service.recordPostLink({ + postId: first.postId, + resource: { + ...resource(first.organizationId, linear), + remoteId: "LIN-99", + displayKey: "LIN-99", + }, + }); + + const firstLinks = yield* service.listPostLinks(first); + expect(firstLinks).toHaveLength(2); + expect(firstLinks.map((link) => link.provider).sort()).toEqual([ + "github", + "linear", + ]); + const secondLinks = yield* service.listPostLinks(second); + expect(secondLinks).toHaveLength(1); + expect(secondLinks[0]?.id).not.toBe( + githubFirst.postExternalResourceLinkId + ); + + const reserved = yield* service.reserveCreation({ + connectionId: github, + organizationId: first.organizationId, + postId: first.postId, + idempotencyKey: "create-github-issue", + }); + const duplicate = yield* service.reserveCreation({ + connectionId: github, + organizationId: first.organizationId, + postId: first.postId, + idempotencyKey: "create-github-issue", + }); + expect(reserved.reserved).toBe(true); + expect(duplicate.reserved).toBe(false); + expect(duplicate.id).toBe(reserved.id); + yield* service.failCreation({ requestId: reserved.id }); + const retried = yield* service.reserveCreation({ + connectionId: github, + organizationId: first.organizationId, + postId: first.postId, + idempotencyKey: "create-github-issue", + }); + expect(retried.reserved).toBe(true); + }) + ); + }); +}); diff --git a/packages/domain/src/integration/external-resource/live.ts b/packages/domain/src/integration/external-resource/live.ts new file mode 100644 index 00000000..2f0313a9 --- /dev/null +++ b/packages/domain/src/integration/external-resource/live.ts @@ -0,0 +1,324 @@ +import { currentDb, schema } from "@feeblo/db"; +import { + asLegid, + ExternalResourceCreateRequestId, + IntegrationExternalResourceId, + PostExternalResourceLinkId, +} from "@feeblo/id"; +import { and, eq } from "drizzle-orm"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import { InternalServerError } from "../../rpc-errors"; +import { + PostExternalResourceLink, + type RecordPostExternalResourceLink, +} from "./schema"; +import { ExternalResourceService } from "./service"; + +const databaseError = (operation: string) => () => + new InternalServerError({ + message: `External resource ${operation} failed.`, + }); + +const decodePostLink = (value: unknown) => + Schema.decodeUnknownEffect(PostExternalResourceLink)(value).pipe( + Effect.mapError(databaseError("row decoding")) + ); + +/** + * A creation reservation older than this is reclaimed so a crashed process + * cannot wedge its idempotency key in `pending` forever. + */ +const creationReservationStaleMs = 60 * 60 * 1000; + +/** Database implementation of provider-neutral external-resource storage. */ +const makeExternalResourceService = Effect.gen(function* () { + const db = yield* currentDb; + const recordPostLink = (input: RecordPostExternalResourceLink) => + db + .transaction(() => + Effect.gen(function* () { + const resourceId = yield* IntegrationExternalResourceId.generate.pipe( + Effect.mapError(databaseError("identifier generation")) + ); + const [resource] = yield* db + .insert(schema.integrationExternalResourceTable) + .values({ + id: resourceId, + organizationId: input.resource.organizationId, + connectionId: input.resource.connectionId, + resourceType: input.resource.resourceType, + remoteId: input.resource.remoteId, + remoteUrl: input.resource.remoteUrl.toString(), + displayKey: input.resource.displayKey, + title: input.resource.title, + stateKey: input.resource.stateKey, + safeMetadata: input.resource.safeMetadata, + }) + .onConflictDoUpdate({ + target: [ + schema.integrationExternalResourceTable.connectionId, + schema.integrationExternalResourceTable.resourceType, + schema.integrationExternalResourceTable.remoteId, + ], + set: { + remoteUrl: input.resource.remoteUrl.toString(), + displayKey: input.resource.displayKey, + title: input.resource.title, + stateKey: input.resource.stateKey, + safeMetadata: input.resource.safeMetadata, + }, + }) + .returning({ id: schema.integrationExternalResourceTable.id }) + .pipe(Effect.mapError(databaseError("resource upsert"))); + if (resource === undefined) { + return yield* new InternalServerError({ + message: "External resource upsert did not return a resource.", + }); + } + const linkId = yield* PostExternalResourceLinkId.generate.pipe( + Effect.mapError(databaseError("link identifier generation")) + ); + const [link] = yield* db + .insert(schema.postExternalResourceLinkTable) + .values({ + id: linkId, + organizationId: input.resource.organizationId, + postId: input.postId, + externalResourceId: resource.id, + }) + .onConflictDoNothing() + .returning({ id: schema.postExternalResourceLinkTable.id }) + .pipe(Effect.mapError(databaseError("post link insert"))); + if (link !== undefined) { + return { + externalResourceId: asLegid(IntegrationExternalResourceId)( + resource.id + ), + postExternalResourceLinkId: asLegid(PostExternalResourceLinkId)( + link.id + ), + }; + } + const existing = yield* db + .select({ id: schema.postExternalResourceLinkTable.id }) + .from(schema.postExternalResourceLinkTable) + .where( + and( + eq(schema.postExternalResourceLinkTable.postId, input.postId), + eq( + schema.postExternalResourceLinkTable.externalResourceId, + resource.id + ) + ) + ) + .limit(1) + .pipe(Effect.mapError(databaseError("post link lookup"))); + const existingLink = existing[0]; + if (existingLink === undefined) { + return yield* new InternalServerError({ + message: + "External resource post link was not found after conflict.", + }); + } + return { + externalResourceId: asLegid(IntegrationExternalResourceId)( + resource.id + ), + postExternalResourceLinkId: asLegid(PostExternalResourceLinkId)( + existingLink.id + ), + }; + }) + ) + .pipe(Effect.mapError(databaseError("record transaction"))); + + return ExternalResourceService.of({ + listPostLinks: (input) => + db + .select({ + id: schema.postExternalResourceLinkTable.id, + connectionId: schema.integrationExternalResourceTable.connectionId, + provider: schema.integrationConnectionTable.provider, + providerDisplayName: schema.integrationConnectionTable.name, + resourceType: schema.integrationExternalResourceTable.resourceType, + remoteUrl: schema.integrationExternalResourceTable.remoteUrl, + displayKey: schema.integrationExternalResourceTable.displayKey, + title: schema.integrationExternalResourceTable.title, + stateKey: schema.integrationExternalResourceTable.stateKey, + safeMetadata: schema.integrationExternalResourceTable.safeMetadata, + }) + .from(schema.postExternalResourceLinkTable) + .innerJoin( + schema.integrationExternalResourceTable, + eq( + schema.integrationExternalResourceTable.id, + schema.postExternalResourceLinkTable.externalResourceId + ) + ) + .innerJoin( + schema.integrationConnectionTable, + eq( + schema.integrationConnectionTable.id, + schema.integrationExternalResourceTable.connectionId + ) + ) + .where( + and( + eq( + schema.postExternalResourceLinkTable.organizationId, + input.organizationId + ), + eq(schema.postExternalResourceLinkTable.postId, input.postId) + ) + ) + .pipe( + Effect.mapError(databaseError("link list")), + Effect.flatMap((rows) => Effect.forEach(rows, decodePostLink)) + ), + recordPostLink, + reserveCreation: (input) => + Effect.gen(function* () { + const id = yield* ExternalResourceCreateRequestId.generate.pipe( + Effect.mapError(databaseError("creation identifier generation")) + ); + const created = yield* db + .insert(schema.externalResourceCreateRequestTable) + .values({ + id, + organizationId: input.organizationId, + connectionId: input.connectionId, + postId: input.postId, + idempotencyKey: input.idempotencyKey, + state: "pending", + }) + .onConflictDoNothing() + .returning({ id: schema.externalResourceCreateRequestTable.id }) + .pipe(Effect.mapError(databaseError("creation reservation"))); + const inserted = created[0]; + if (inserted !== undefined) { + return { + id: asLegid(ExternalResourceCreateRequestId)(inserted.id), + reserved: true, + postExternalResourceLinkId: null, + }; + } + const [existing] = yield* db + .select({ + id: schema.externalResourceCreateRequestTable.id, + postId: schema.externalResourceCreateRequestTable.postId, + postExternalResourceLinkId: + schema.externalResourceCreateRequestTable + .postExternalResourceLinkId, + state: schema.externalResourceCreateRequestTable.state, + createdAt: schema.externalResourceCreateRequestTable.createdAt, + }) + .from(schema.externalResourceCreateRequestTable) + .where( + and( + eq( + schema.externalResourceCreateRequestTable.connectionId, + input.connectionId + ), + eq( + schema.externalResourceCreateRequestTable.organizationId, + input.organizationId + ), + eq( + schema.externalResourceCreateRequestTable.idempotencyKey, + input.idempotencyKey + ) + ) + ) + .limit(1) + .pipe(Effect.mapError(databaseError("creation reservation lookup"))); + if (existing === undefined || existing.postId !== input.postId) { + return yield* new InternalServerError({ + message: "External resource creation reservation was not found.", + }); + } + const stale = + existing.state === "pending" && + existing.createdAt.getTime() < + Date.now() - creationReservationStaleMs; + if (existing.state === "failed" || stale) { + const reclaimedAt = new Date(); + yield* db + .update(schema.externalResourceCreateRequestTable) + .set({ + state: "pending", + externalResourceId: null, + postExternalResourceLinkId: null, + createdAt: reclaimedAt, + updatedAt: reclaimedAt, + }) + .where( + and( + eq(schema.externalResourceCreateRequestTable.id, existing.id), + eq( + schema.externalResourceCreateRequestTable.state, + existing.state + ) + ) + ) + .pipe( + Effect.mapError(databaseError("creation reservation reclaim")) + ); + return { + id: asLegid(ExternalResourceCreateRequestId)(existing.id), + reserved: true, + postExternalResourceLinkId: null, + }; + } + return { + id: asLegid(ExternalResourceCreateRequestId)(existing.id), + reserved: false, + postExternalResourceLinkId: + existing.postExternalResourceLinkId === null + ? null + : asLegid(PostExternalResourceLinkId)( + existing.postExternalResourceLinkId + ), + }; + }), + failCreation: (input) => + db + .update(schema.externalResourceCreateRequestTable) + .set({ state: "failed", updatedAt: new Date() }) + .where( + and( + eq(schema.externalResourceCreateRequestTable.id, input.requestId), + eq(schema.externalResourceCreateRequestTable.state, "pending") + ) + ) + .pipe( + Effect.mapError(databaseError("creation release")), + Effect.asVoid + ), + completeCreation: (input) => + db + .update(schema.externalResourceCreateRequestTable) + .set({ + state: "succeeded", + externalResourceId: input.externalResourceId, + postExternalResourceLinkId: input.postExternalResourceLinkId, + }) + .where( + and( + eq(schema.externalResourceCreateRequestTable.id, input.requestId), + eq(schema.externalResourceCreateRequestTable.state, "pending") + ) + ) + .pipe( + Effect.mapError(databaseError("creation completion")), + Effect.asVoid + ), + }); +}); + +/** Live external-resource storage service. */ +export const ExternalResourceServiceLive = Layer.effect( + ExternalResourceService, + makeExternalResourceService +); diff --git a/packages/domain/src/integration/external-resource/rpcs.ts b/packages/domain/src/integration/external-resource/rpcs.ts new file mode 100644 index 00000000..6e7f4044 --- /dev/null +++ b/packages/domain/src/integration/external-resource/rpcs.ts @@ -0,0 +1,15 @@ +import * as Schema from "effect/Schema"; +import * as Rpc from "effect/unstable/rpc/Rpc"; +import * as RpcGroup from "effect/unstable/rpc/RpcGroup"; +import { AuthMiddleware } from "../../session-middleware"; +import { WebhookManagementErrors } from "../errors"; +import * as S from "./schema"; + +/** Generic authenticated read surface for links from a Feeblo post to provider resources. */ +export class ExternalResourceRpcs extends RpcGroup.make( + Rpc.make("PostExternalResourceLinkList", { + success: Schema.Array(S.PostExternalResourceLink), + payload: S.PostExternalResourceLinkList, + error: WebhookManagementErrors, + }).middleware(AuthMiddleware) +) {} diff --git a/packages/domain/src/integration/external-resource/schema.ts b/packages/domain/src/integration/external-resource/schema.ts new file mode 100644 index 00000000..a2ad720b --- /dev/null +++ b/packages/domain/src/integration/external-resource/schema.ts @@ -0,0 +1,109 @@ +import { + IntegrationExternalResourceType, + IntegrationProviderKey, + IntegrationSafeDisplayMetadata, +} from "@feeblo/db/validation-schema/integration"; +import { + ExternalResourceCreateRequestId, + IntegrationConnectionId, + IntegrationExternalResourceId, + PostExternalResourceLinkId, + PostId, + WorkspaceId, +} from "@feeblo/id"; +import * as Schema from "effect/Schema"; + +/** Safe linked-resource details rendered on a Feeblo post. */ +export const PostExternalResourceLink = Schema.Struct({ + id: PostExternalResourceLinkId.schema, + connectionId: IntegrationConnectionId.schema, + provider: IntegrationProviderKey, + providerDisplayName: Schema.String, + resourceType: IntegrationExternalResourceType, + remoteUrl: Schema.URLFromString, + displayKey: Schema.NullOr(Schema.String), + title: Schema.NullOr(Schema.String), + stateKey: Schema.NullOr(Schema.String), + safeMetadata: IntegrationSafeDisplayMetadata, +}); +export type PostExternalResourceLink = Schema.Schema.Type< + typeof PostExternalResourceLink +>; + +/** Authenticated request for every provider resource linked to one post. */ +export const PostExternalResourceLinkList = Schema.Struct({ + organizationId: WorkspaceId.schema, + postId: PostId.schema, +}); +export type PostExternalResourceLinkList = Schema.Schema.Type< + typeof PostExternalResourceLinkList +>; + +/** Provider-normalized resource values accepted by the generic persistence capability. */ +export const ExternalResourceRecord = Schema.Struct({ + organizationId: WorkspaceId.schema, + connectionId: IntegrationConnectionId.schema, + resourceType: IntegrationExternalResourceType, + remoteId: Schema.NonEmptyString, + remoteUrl: Schema.URLFromString, + displayKey: Schema.NullOr(Schema.String), + title: Schema.NullOr(Schema.String), + stateKey: Schema.NullOr(Schema.String), + safeMetadata: IntegrationSafeDisplayMetadata, +}); +export type ExternalResourceRecord = Schema.Schema.Type< + typeof ExternalResourceRecord +>; + +/** Link a normalized provider resource to a Feeblo post. */ +export const RecordPostExternalResourceLink = Schema.Struct({ + postId: PostId.schema, + resource: ExternalResourceRecord, +}); +export type RecordPostExternalResourceLink = Schema.Schema.Type< + typeof RecordPostExternalResourceLink +>; + +/** Result from recording a resource and its post link. */ +export const RecordedPostExternalResourceLink = Schema.Struct({ + externalResourceId: IntegrationExternalResourceId.schema, + postExternalResourceLinkId: PostExternalResourceLinkId.schema, +}); +export type RecordedPostExternalResourceLink = Schema.Schema.Type< + typeof RecordedPostExternalResourceLink +>; + +export const ExternalResourceCreationReservation = Schema.Struct({ + organizationId: WorkspaceId.schema, + connectionId: IntegrationConnectionId.schema, + postId: PostId.schema, + idempotencyKey: Schema.NonEmptyString, +}); +export type ExternalResourceCreationReservation = Schema.Schema.Type< + typeof ExternalResourceCreationReservation +>; + +export const ExternalResourceCreationReservationResult = Schema.Struct({ + id: ExternalResourceCreateRequestId.schema, + reserved: Schema.Boolean, + postExternalResourceLinkId: Schema.NullOr(PostExternalResourceLinkId.schema), +}); +export type ExternalResourceCreationReservationResult = Schema.Schema.Type< + typeof ExternalResourceCreationReservationResult +>; + +export const ExternalResourceCreationCompletion = Schema.Struct({ + requestId: ExternalResourceCreateRequestId.schema, + externalResourceId: IntegrationExternalResourceId.schema, + postExternalResourceLinkId: PostExternalResourceLinkId.schema, +}); +export type ExternalResourceCreationCompletion = Schema.Schema.Type< + typeof ExternalResourceCreationCompletion +>; + +export const ExternalResourceCreationFailure = Schema.Struct({ + requestId: ExternalResourceCreateRequestId.schema, +}); +export type ExternalResourceCreationFailure = Schema.Schema.Type< + typeof ExternalResourceCreationFailure +>; diff --git a/packages/domain/src/integration/external-resource/service.ts b/packages/domain/src/integration/external-resource/service.ts new file mode 100644 index 00000000..7c049deb --- /dev/null +++ b/packages/domain/src/integration/external-resource/service.ts @@ -0,0 +1,35 @@ +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type { InternalServerError } from "../../rpc-errors"; +import type * as S from "./schema"; + +/** Provider-neutral persistence capability for external resources and their Feeblo-post links. */ +export interface ExternalResourceServiceShape { + readonly completeCreation: ( + input: S.ExternalResourceCreationCompletion + ) => Effect.Effect; + readonly failCreation: ( + input: S.ExternalResourceCreationFailure + ) => Effect.Effect; + readonly listPostLinks: ( + input: S.PostExternalResourceLinkList + ) => Effect.Effect< + readonly S.PostExternalResourceLink[], + InternalServerError + >; + readonly recordPostLink: ( + input: S.RecordPostExternalResourceLink + ) => Effect.Effect; + readonly reserveCreation: ( + input: S.ExternalResourceCreationReservation + ) => Effect.Effect< + S.ExternalResourceCreationReservationResult, + InternalServerError + >; +} + +/** Service key for generic external-resource persistence. */ +export class ExternalResourceService extends Context.Service< + ExternalResourceService, + ExternalResourceServiceShape +>()("@feeblo/ExternalResourceService") {} diff --git a/packages/domain/src/integration/github/config.ts b/packages/domain/src/integration/github/config.ts new file mode 100644 index 00000000..ed2e6c3b --- /dev/null +++ b/packages/domain/src/integration/github/config.ts @@ -0,0 +1,14 @@ +import * as Context from "effect/Context"; + +/** Deployment GitHub App configuration held outside connection and route JSON. */ +export interface GitHubIntegrationConfigShape { + /** GitHub App client identifier; never persisted in integration JSON. */ + readonly clientId: string; + readonly configured: boolean; +} + +/** Server configuration capability for GitHub App setup; secrets remain in its implementation only. */ +export class GitHubIntegrationConfig extends Context.Service< + GitHubIntegrationConfig, + GitHubIntegrationConfigShape +>()("@feeblo/GitHubIntegrationConfig") {} diff --git a/packages/domain/src/integration/github/errors.ts b/packages/domain/src/integration/github/errors.ts new file mode 100644 index 00000000..60202717 --- /dev/null +++ b/packages/domain/src/integration/github/errors.ts @@ -0,0 +1,20 @@ +import * as Schema from "effect/Schema"; +import { PolicyDeniedError } from "../../policy"; +import { + BadRequestError, + InternalServerError, + NotFoundError, + UnauthorizedError, +} from "../../rpc-errors"; + +/** RPC failures that can be safely rendered to GitHub integration users. */ +export const GitHubIntegrationErrors = Schema.Union([ + BadRequestError, + InternalServerError, + NotFoundError, + PolicyDeniedError, + UnauthorizedError, +]); +export type GitHubIntegrationError = Schema.Schema.Type< + typeof GitHubIntegrationErrors +>; diff --git a/packages/domain/src/integration/github/github-provider.ts b/packages/domain/src/integration/github/github-provider.ts new file mode 100644 index 00000000..559a7d49 --- /dev/null +++ b/packages/domain/src/integration/github/github-provider.ts @@ -0,0 +1,52 @@ +import type { LegidOf } from "@feeblo/id"; +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type { GitHubIntegrationError } from "./errors"; +import type { + GitHubAppInstallationCallback, + GitHubConnectStarted, + GitHubPostIssueCreate, + GitHubPostIssueLink, + GitHubRepository, + GitHubResolvedIssue, +} from "./schema"; + +/** Narrow GitHub I/O capability implemented by the provider adapter; persistence and policy stay in the domain service. */ +export interface GitHubProviderShape { + /** Verifies installer access to the App installation without persisting either temporary token. */ + readonly completeInstallation: ( + input: GitHubAppInstallationCallback + ) => Effect.Effect< + { readonly organizationId: string }, + GitHubIntegrationError + >; + /** Creates a GitHub issue using the stable idempotency key as its external request identity. */ + readonly createIssue: ( + input: GitHubPostIssueCreate & { + readonly postDescription: string | null; + readonly postTitle: string | null; + readonly postUrl: URL; + } + ) => Effect.Effect; + readonly listRepositories: (input: { + readonly connectionId: string; + }) => Effect.Effect; + /** Resolves an existing issue and writes a Feeblo bot comment backlink before persistence. */ + readonly resolveIssue: ( + input: GitHubPostIssueLink & { readonly postUrl: URL } + ) => Effect.Effect; + /** Starts an installation of the Feeblo GitHub App for one organization. */ + readonly startInstallation: ( + organizationId: LegidOf<"WorkspaceId"> + ) => Effect.Effect; + /** Uninstalls the App from GitHub before Feeblo archives the connection. */ + readonly uninstallInstallation: (input: { + readonly connectionId: string; + }) => Effect.Effect; +} + +/** Provider adapter key selected by server composition. */ +export class GitHubProvider extends Context.Service< + GitHubProvider, + GitHubProviderShape +>()("@feeblo/GitHubProvider") {} diff --git a/packages/domain/src/integration/github/handlers.ts b/packages/domain/src/integration/github/handlers.ts new file mode 100644 index 00000000..c74edf8e --- /dev/null +++ b/packages/domain/src/integration/github/handlers.ts @@ -0,0 +1,50 @@ +import * as Effect from "effect/Effect"; +import * as Policy from "../../policy"; +import { GitHubManagementService } from "./management-service"; +import { GitHubManagementRpcs } from "./rpcs"; + +/** RPC handlers authorize GitHub management before delegating provider-specific work to the application service. */ +export const GitHubManagementRpcHandlersEffect = Effect.gen(function* () { + const service = yield* GitHubManagementService; + const authorize = (organizationId: string) => + Policy.withPolicy( + Policy.canPermission(organizationId, "integrations.manage") + ); + return { + GitHubIntegrationStatus: () => service.status(), + GitHubConnectionList: ( + input: Parameters[0] + ) => service.listConnections(input).pipe(authorize(input.organizationId)), + GitHubConnectStart: (input: Parameters[0]) => + service.connectStart(input).pipe(authorize(input.organizationId)), + GitHubConnectionDisconnect: ( + input: Parameters[0] + ) => service.disconnect(input).pipe(authorize(input.organizationId)), + GitHubRepositoryList: ( + input: Parameters[0] + ) => service.listRepositories(input).pipe(authorize(input.organizationId)), + GitHubSettingsGet: (input: Parameters[0]) => + service.getSettings(input).pipe(authorize(input.organizationId)), + GitHubSettingsUpdate: ( + input: Parameters[0] + ) => service.updateSettings(input).pipe(authorize(input.organizationId)), + GitHubRuleList: (input: Parameters[0]) => + service.listRules(input).pipe(authorize(input.organizationId)), + GitHubRuleCreate: (input: Parameters[0]) => + service.createRule(input).pipe(authorize(input.organizationId)), + GitHubRuleUpdate: (input: Parameters[0]) => + service.updateRule(input).pipe(authorize(input.organizationId)), + GitHubRuleDelete: (input: Parameters[0]) => + service.deleteRule(input).pipe(authorize(input.organizationId)), + GitHubPostIssueCreate: ( + input: Parameters[0] + ) => service.createPostIssue(input).pipe(authorize(input.organizationId)), + GitHubPostIssueLink: (input: Parameters[0]) => + service.linkPostIssue(input).pipe(authorize(input.organizationId)), + }; +}); + +/** RPC layer deliberately leaves the server-selected GitHub adapter dependency unresolved. */ +export const GitHubManagementRpcHandlers = GitHubManagementRpcs.toLayer( + GitHubManagementRpcHandlersEffect +); diff --git a/packages/domain/src/integration/github/inbound-live.test.ts b/packages/domain/src/integration/github/inbound-live.test.ts new file mode 100644 index 00000000..a64cb2ad --- /dev/null +++ b/packages/domain/src/integration/github/inbound-live.test.ts @@ -0,0 +1,471 @@ +import { describe, expect, layer } from "@effect/vitest"; +import { currentDb, Database, schema } from "@feeblo/db"; +import { + IntegrationExternalResourceType, + IntegrationProviderKey, +} from "@feeblo/db/validation-schema/integration"; +import { + BoardId, + GitHubSyncRuleId, + IntegrationConnectionId, + IntegrationExternalResourceId, + PostExternalResourceLinkId, + PostId, + PostStatusId, + WorkspaceId, +} from "@feeblo/id"; +import { IntegrationEventRecorderLive } from "@feeblo/integration-core"; +import { eq } from "drizzle-orm"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import { EmailOutboxConfig } from "../../email-outbox/config"; +import { NotificationService } from "../../notification/service"; +import { PostRepository } from "../../post/repository"; +import { GitHubInboundServiceLive } from "./inbound-live"; +import { GitHubInboundService } from "./inbound-service"; + +const TestLayer = Layer.mergeAll( + GitHubInboundServiceLive.pipe( + Layer.provide(NotificationService.layer), + Layer.provide(IntegrationEventRecorderLive), + Layer.provide(PostRepository.layer), + Layer.provide( + EmailOutboxConfig.layerTest(new URL("https://feeblo.example")) + ), + Layer.provide(Database.PgliteDatabaseLive) + ), + Database.PgliteDatabaseLive +); + +const seedRuleScenario = ({ + linkedIssueStates, + rules, +}: { + readonly linkedIssueStates: readonly ("open" | "closed")[]; + readonly rules: readonly { + readonly issueMatchMode: "all" | "any"; + readonly issueState: "open" | "closed"; + readonly targetStatus: "completed" | "closed"; + readonly enabled?: boolean; + readonly createdAt?: Date; + }[]; +}) => + Effect.gen(function* () { + const db = yield* currentDb; + const now = new Date(); + const organizationId = yield* WorkspaceId.generate; + const boardId = yield* BoardId.generate; + const openStatusId = yield* PostStatusId.generate; + const completedStatusId = yield* PostStatusId.generate; + const closedStatusId = yield* PostStatusId.generate; + const postId = yield* PostId.generate; + const connectionId = yield* IntegrationConnectionId.generate; + const installationId = `gh-installation-${organizationId}`; + + yield* db.insert(schema.organizationTable).values({ + id: organizationId, + name: "GitHub rule test", + slug: `github-rule-${organizationId}`, + createdAt: now, + }); + yield* db.insert(schema.boardTable).values({ + id: boardId, + organizationId, + name: "Feedback", + slug: "feedback", + visibility: "PRIVATE", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.postStatusTable).values([ + { + id: openStatusId, + organizationId, + type: "PENDING", + orderIndex: 0, + }, + { + id: completedStatusId, + organizationId, + type: "COMPLETED", + orderIndex: 1, + }, + { + id: closedStatusId, + organizationId, + type: "CLOSED", + orderIndex: 2, + }, + ]); + yield* db.insert(schema.postTable).values({ + id: postId, + organizationId, + boardId, + statusId: openStatusId, + title: "Close this post", + slug: "close-this-post", + content: "Content", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.integrationConnectionTable).values({ + id: connectionId, + organizationId, + provider: IntegrationProviderKey.make("github"), + name: "GitHub installation", + lifecycle: "active", + }); + yield* db.insert(schema.githubInstallationTable).values({ + connectionId, + installationId, + accountId: "67890", + accountLogin: "feeblo-test", + accountType: "Organization", + }); + + for (const [index, stateKey] of linkedIssueStates.entries()) { + const externalResourceId = yield* IntegrationExternalResourceId.generate; + const linkId = yield* PostExternalResourceLinkId.generate; + const issueNumber = index + 10; + yield* db.insert(schema.integrationExternalResourceTable).values({ + id: externalResourceId, + organizationId, + connectionId, + resourceType: IntegrationExternalResourceType.make("issue"), + remoteId: `I_test_${issueNumber}`, + remoteUrl: `https://github.com/feeblo/test/issues/${issueNumber}`, + displayKey: `feeblo/test#${issueNumber}`, + title: "Linked issue", + stateKey, + safeMetadata: { + issueNumber, + repositoryName: "test", + repositoryOwner: "feeblo", + }, + }); + yield* db.insert(schema.postExternalResourceLinkTable).values({ + id: linkId, + organizationId, + postId, + externalResourceId, + }); + } + + const targetStatusIds = { + completed: completedStatusId, + closed: closedStatusId, + } as const; + + for (const rule of rules) { + const ruleId = yield* GitHubSyncRuleId.generate; + yield* db.insert(schema.githubSyncRuleTable).values({ + id: ruleId, + organizationId, + connectionId, + issueMatchMode: rule.issueMatchMode, + issueState: rule.issueState, + postStatusId: targetStatusIds[rule.targetStatus], + upvoterNotificationPolicy: "do_not_notify_upvoters", + enabled: rule.enabled ?? true, + ...(rule.createdAt === undefined ? {} : { createdAt: rule.createdAt }), + }); + } + + return { + closedStatusId, + completedStatusId, + installationId, + openStatusId, + postId, + }; + }); + +describe("GitHub inbound synchronization", () => { + layer(TestLayer)("issue status rules", (it) => { + it.effect("sets the Feeblo status when a linked GitHub issue closes", () => + Effect.gen(function* () { + const db = yield* currentDb; + const inbound = yield* GitHubInboundService; + const organizationId = yield* WorkspaceId.generate; + const boardId = yield* BoardId.generate; + const openStatusId = yield* PostStatusId.generate; + const closedStatusId = yield* PostStatusId.generate; + const postId = yield* PostId.generate; + const connectionId = yield* IntegrationConnectionId.generate; + const externalResourceId = + yield* IntegrationExternalResourceId.generate; + const linkId = yield* PostExternalResourceLinkId.generate; + const ruleId = yield* GitHubSyncRuleId.generate; + const now = new Date(); + + yield* db.insert(schema.organizationTable).values({ + id: organizationId, + name: "GitHub rule test", + slug: `github-rule-${organizationId}`, + createdAt: now, + }); + yield* db.insert(schema.boardTable).values({ + id: boardId, + organizationId, + name: "Feedback", + slug: "feedback", + visibility: "PRIVATE", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.postStatusTable).values([ + { + id: openStatusId, + organizationId, + type: "PENDING", + orderIndex: 0, + }, + { + id: closedStatusId, + organizationId, + type: "CLOSED", + orderIndex: 1, + }, + ]); + yield* db.insert(schema.postTable).values({ + id: postId, + organizationId, + boardId, + statusId: openStatusId, + title: "Close this post", + slug: "close-this-post", + content: "Content", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.integrationConnectionTable).values({ + id: connectionId, + organizationId, + provider: IntegrationProviderKey.make("github"), + name: "GitHub installation", + lifecycle: "active", + }); + yield* db.insert(schema.githubInstallationTable).values({ + connectionId, + installationId: "12345", + accountId: "67890", + accountLogin: "feeblo-test", + accountType: "Organization", + }); + yield* db.insert(schema.integrationExternalResourceTable).values({ + id: externalResourceId, + organizationId, + connectionId, + resourceType: IntegrationExternalResourceType.make("issue"), + remoteId: "I_test", + remoteUrl: "https://github.com/feeblo/test/issues/12", + displayKey: "feeblo/test#12", + title: "Linked issue", + stateKey: "open", + safeMetadata: { + issueNumber: 12, + repositoryName: "test", + repositoryOwner: "feeblo", + }, + }); + yield* db.insert(schema.postExternalResourceLinkTable).values({ + id: linkId, + organizationId, + postId, + externalResourceId, + }); + yield* db.insert(schema.githubSyncRuleTable).values({ + id: ruleId, + organizationId, + connectionId, + issueMatchMode: "all", + issueState: "closed", + postStatusId: closedStatusId, + upvoterNotificationPolicy: "do_not_notify_upvoters", + enabled: true, + }); + + yield* inbound.applyIssueWebhook({ + deliveryId: "delivery-close-12", + eventName: "issues", + installationId: "12345", + issueNumber: 12, + issueState: "closed", + repositoryName: "test", + repositoryOwner: "feeblo", + }); + + const [post] = yield* db + .select({ statusId: schema.postTable.statusId }) + .from(schema.postTable) + .where(eq(schema.postTable.id, postId)); + expect(post?.statusId).toBe(closedStatusId); + }) + ); + + it.effect( + "sets the Feeblo status only once all linked issues match an all rule", + () => + Effect.gen(function* () { + const db = yield* currentDb; + const inbound = yield* GitHubInboundService; + const seeded = yield* seedRuleScenario({ + linkedIssueStates: ["open", "open"], + rules: [ + { + issueMatchMode: "all", + issueState: "closed", + targetStatus: "closed", + }, + ], + }); + + yield* inbound.applyIssueWebhook({ + deliveryId: "delivery-close-first", + eventName: "issues", + installationId: seeded.installationId, + issueNumber: 10, + issueState: "closed", + repositoryName: "test", + repositoryOwner: "feeblo", + }); + const [afterFirst] = yield* db + .select({ statusId: schema.postTable.statusId }) + .from(schema.postTable) + .where(eq(schema.postTable.id, seeded.postId)); + expect(afterFirst?.statusId).toBe(seeded.openStatusId); + + yield* inbound.applyIssueWebhook({ + deliveryId: "delivery-close-second", + eventName: "issues", + installationId: seeded.installationId, + issueNumber: 11, + issueState: "closed", + repositoryName: "test", + repositoryOwner: "feeblo", + }); + const [afterSecond] = yield* db + .select({ statusId: schema.postTable.statusId }) + .from(schema.postTable) + .where(eq(schema.postTable.id, seeded.postId)); + expect(afterSecond?.statusId).toBe(seeded.closedStatusId); + }) + ); + + it.effect( + "switches to the any-open rule when a linked issue is reopened", + () => + Effect.gen(function* () { + const db = yield* currentDb; + const inbound = yield* GitHubInboundService; + const seeded = yield* seedRuleScenario({ + linkedIssueStates: ["closed", "closed"], + rules: [ + { + issueMatchMode: "all", + issueState: "closed", + targetStatus: "completed", + }, + { + issueMatchMode: "any", + issueState: "open", + targetStatus: "closed", + }, + ], + }); + + yield* inbound.applyIssueWebhook({ + deliveryId: "delivery-reopen", + eventName: "issues", + installationId: seeded.installationId, + issueNumber: 10, + issueState: "open", + repositoryName: "test", + repositoryOwner: "feeblo", + }); + const [post] = yield* db + .select({ statusId: schema.postTable.statusId }) + .from(schema.postTable) + .where(eq(schema.postTable.id, seeded.postId)); + expect(post?.statusId).toBe(seeded.closedStatusId); + }) + ); + + it.effect( + "ignores a disabled rule and applies an enabled matching rule", + () => + Effect.gen(function* () { + const db = yield* currentDb; + const inbound = yield* GitHubInboundService; + const seeded = yield* seedRuleScenario({ + linkedIssueStates: ["closed"], + rules: [ + { + issueMatchMode: "all", + issueState: "closed", + targetStatus: "completed", + enabled: false, + }, + { + issueMatchMode: "any", + issueState: "open", + targetStatus: "closed", + }, + ], + }); + + yield* inbound.applyIssueWebhook({ + deliveryId: "delivery-disabled-rule", + eventName: "issues", + installationId: seeded.installationId, + issueNumber: 10, + issueState: "open", + repositoryName: "test", + repositoryOwner: "feeblo", + }); + const [post] = yield* db + .select({ statusId: schema.postTable.statusId }) + .from(schema.postTable) + .where(eq(schema.postTable.id, seeded.postId)); + expect(post?.statusId).toBe(seeded.closedStatusId); + }) + ); + + it.effect("ignores a non-matching rule and applies the matching rule", () => + Effect.gen(function* () { + const db = yield* currentDb; + const inbound = yield* GitHubInboundService; + const seeded = yield* seedRuleScenario({ + linkedIssueStates: ["open"], + rules: [ + { + issueMatchMode: "all", + issueState: "closed", + targetStatus: "closed", + }, + { + issueMatchMode: "any", + issueState: "open", + targetStatus: "completed", + }, + ], + }); + + yield* inbound.applyIssueWebhook({ + deliveryId: "delivery-multi-rule-nonmatching", + eventName: "issues", + installationId: seeded.installationId, + issueNumber: 10, + issueState: "open", + repositoryName: "test", + repositoryOwner: "feeblo", + }); + const [post] = yield* db + .select({ statusId: schema.postTable.statusId }) + .from(schema.postTable) + .where(eq(schema.postTable.id, seeded.postId)); + expect(post?.statusId).toBe(seeded.completedStatusId); + }) + ); + }); +}); diff --git a/packages/domain/src/integration/github/inbound-live.ts b/packages/domain/src/integration/github/inbound-live.ts new file mode 100644 index 00000000..a1926621 --- /dev/null +++ b/packages/domain/src/integration/github/inbound-live.ts @@ -0,0 +1,454 @@ +import { + currentDb, + Database, + gitHubIssueSafeMetadataConditions, + schema, +} from "@feeblo/db"; +import { + IntegrationExternalResourceType, + IntegrationProviderKey, +} from "@feeblo/db/validation-schema/integration"; +import { + asLegid, + BoardId, + GitHubSyncRuleId, + GitHubWebhookDeliveryId, + IntegrationConnectionId, + PostId, + PostStatusId, + WorkspaceId, +} from "@feeblo/id"; +import { IntegrationEventRecorder } from "@feeblo/integration-core"; +import { and, asc, eq } from "drizzle-orm"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Schema from "effect/Schema"; +import { EmailOutboxConfig } from "../../email-outbox/config"; +import { NotificationService } from "../../notification/service"; +import { PostRepository } from "../../post/repository"; +import { InternalServerError, NotFoundError } from "../../rpc-errors"; +import { recordPostIntegrationEvent } from "../post-event-recording"; +import { GitHubInboundService } from "./inbound-service"; +import { findMatchingGitHubSyncRules } from "./rule-evaluation"; + +const inboundDatabaseError = (operation: string) => () => + new InternalServerError({ message: `GitHub webhook ${operation} failed.` }); + +const gitHubIssueResourceType = IntegrationExternalResourceType.make("issue"); + +/** Applies verified GitHub issue webhooks through a transactional inbox before changing a linked Feeblo post. */ +const makeGitHubInboundService = Effect.gen(function* () { + const db = yield* currentDb; + const notifications = yield* NotificationService; + const integrationEventRecorder = yield* IntegrationEventRecorder; + const emailOutboxConfig = yield* EmailOutboxConfig; + const postRepository = yield* PostRepository; + const activeConnectionForInstallation = (installationId: string) => + db + .select({ + id: schema.integrationConnectionTable.id, + organizationId: schema.integrationConnectionTable.organizationId, + }) + .from(schema.integrationConnectionTable) + .innerJoin( + schema.githubInstallationTable, + eq( + schema.githubInstallationTable.connectionId, + schema.integrationConnectionTable.id + ) + ) + .where( + and( + eq(schema.githubInstallationTable.installationId, installationId), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ), + eq(schema.integrationConnectionTable.lifecycle, "active") + ) + ) + .limit(1) + .pipe( + Effect.mapError(inboundDatabaseError("installation connection lookup")), + Effect.flatMap((rows) => + rows[0] === undefined + ? new NotFoundError({ + message: "Active GitHub App installation was not found.", + }) + : Effect.succeed(rows[0]) + ) + ); + return GitHubInboundService.of({ + applyIssueWebhook: (webhook) => + db + .transaction(() => + Effect.gen(function* () { + const activeConnection = yield* activeConnectionForInstallation( + webhook.installationId + ); + const inboxId = yield* GitHubWebhookDeliveryId.generate; + const inserted = yield* db + .insert(schema.githubWebhookDeliveryTable) + .values({ + id: inboxId, + connectionId: activeConnection.id, + deliveryId: webhook.deliveryId, + eventName: webhook.eventName, + }) + .onConflictDoNothing() + .returning({ id: schema.githubWebhookDeliveryTable.id }) + .pipe(Effect.mapError(inboundDatabaseError("inbox record"))); + if (inserted.length === 0) { + return; + } + const links = yield* db + .select({ + externalResourceId: schema.integrationExternalResourceTable.id, + postId: schema.postExternalResourceLinkTable.postId, + }) + .from(schema.integrationExternalResourceTable) + .innerJoin( + schema.postExternalResourceLinkTable, + eq( + schema.postExternalResourceLinkTable.externalResourceId, + schema.integrationExternalResourceTable.id + ) + ) + .where( + and( + eq( + schema.integrationExternalResourceTable.connectionId, + activeConnection.id + ), + eq( + schema.integrationExternalResourceTable.resourceType, + gitHubIssueResourceType + ), + ...gitHubIssueSafeMetadataConditions({ + issueNumber: webhook.issueNumber, + repositoryName: webhook.repositoryName, + repositoryOwner: webhook.repositoryOwner, + }) + ) + ) + .pipe(Effect.mapError(inboundDatabaseError("issue link lookup"))); + for (const link of links) { + yield* db + .update(schema.integrationExternalResourceTable) + .set({ stateKey: webhook.issueState }) + .where( + eq( + schema.integrationExternalResourceTable.id, + link.externalResourceId + ) + ) + .pipe( + Effect.mapError(inboundDatabaseError("issue state update")) + ); + const allLinks = yield* db + .select({ + stateKey: schema.integrationExternalResourceTable.stateKey, + }) + .from(schema.postExternalResourceLinkTable) + .innerJoin( + schema.integrationExternalResourceTable, + eq( + schema.postExternalResourceLinkTable.externalResourceId, + schema.integrationExternalResourceTable.id + ) + ) + .where( + and( + eq( + schema.integrationExternalResourceTable.connectionId, + activeConnection.id + ), + eq( + schema.postExternalResourceLinkTable.postId, + link.postId + ), + eq( + schema.integrationExternalResourceTable.resourceType, + gitHubIssueResourceType + ) + ) + ) + .pipe( + Effect.mapError( + inboundDatabaseError("linked issue aggregation") + ) + ); + const rules = yield* db + .select() + .from(schema.githubSyncRuleTable) + .where( + and( + eq( + schema.githubSyncRuleTable.connectionId, + activeConnection.id + ), + eq(schema.githubSyncRuleTable.enabled, true) + ) + ) + .orderBy( + asc(schema.githubSyncRuleTable.createdAt), + asc(schema.githubSyncRuleTable.id) + ) + .pipe( + Effect.mapError( + inboundDatabaseError("synchronization rule lookup") + ) + ); + const matches = findMatchingGitHubSyncRules( + rules.map((rule) => ({ + id: asLegid(GitHubSyncRuleId)(rule.id), + connectionId: asLegid(IntegrationConnectionId)( + rule.connectionId + ), + issueMatchMode: rule.issueMatchMode, + issueState: rule.issueState, + postStatusId: asLegid(PostStatusId)(rule.postStatusId), + upvoterNotificationPolicy: rule.upvoterNotificationPolicy, + enabled: rule.enabled, + })), + allLinks.flatMap((item) => + item.stateKey === "open" || item.stateKey === "closed" + ? [item.stateKey] + : [] + ) + ); + const match = matches[0]; + if (match === undefined) { + continue; + } + const post = yield* db + .select({ + boardId: schema.postTable.boardId, + slug: schema.postTable.slug, + statusId: schema.postTable.statusId, + title: schema.postTable.title, + }) + .from(schema.postTable) + .where( + and( + eq(schema.postTable.id, link.postId), + eq( + schema.postTable.organizationId, + activeConnection.organizationId + ) + ) + ) + .limit(1) + .pipe(Effect.mapError(inboundDatabaseError("post lookup"))); + if ( + post[0] === undefined || + post[0].statusId === match.postStatusId + ) { + continue; + } + yield* db + .update(schema.postTable) + .set({ statusId: match.postStatusId }) + .where(eq(schema.postTable.id, link.postId)) + .pipe( + Effect.mapError(inboundDatabaseError("post status update")) + ); + yield* recordPostIntegrationEvent({ + actor: { kind: "end_user" }, + boardId: asLegid(BoardId)(post[0].boardId), + eventType: "feedback.post.status_changed", + organizationId: asLegid(WorkspaceId)( + activeConnection.organizationId + ), + postId: asLegid(PostId)(link.postId), + postSlug: post[0].slug, + previousStatusId: asLegid(PostStatusId)(post[0].statusId), + statusId: match.postStatusId, + title: post[0].title, + }).pipe( + Effect.provideService( + IntegrationEventRecorder, + integrationEventRecorder + ), + Effect.provideService(EmailOutboxConfig, emailOutboxConfig), + Effect.provideService(PostRepository, postRepository), + Effect.provideService(Database.Database, db), + Effect.mapError(inboundDatabaseError("status event recording")) + ); + if (match.upvoterNotificationPolicy === "notify_upvoters") { + yield* notifications + .notifyPostStatusChangedUpvoters({ + organizationId: activeConnection.organizationId, + postId: link.postId, + deduplicationKey: `github.issue.status:${webhook.deliveryId}:${link.postId}:${match.id}`, + }) + .pipe( + Effect.mapError( + inboundDatabaseError("upvoter notification") + ) + ); + } + } + }) + ) + .pipe( + Effect.mapError((error) => + Schema.is(NotFoundError)(error) + ? error + : inboundDatabaseError("transaction")() + ) + ), + applyInstallationLifecycleWebhook: (webhook) => + db + .transaction(() => + Effect.gen(function* () { + const installations = yield* db + .select({ + connectionId: schema.githubInstallationTable.connectionId, + lifecycle: schema.integrationConnectionTable.lifecycle, + suspendedAt: schema.githubInstallationTable.suspendedAt, + }) + .from(schema.githubInstallationTable) + .innerJoin( + schema.integrationConnectionTable, + eq( + schema.integrationConnectionTable.id, + schema.githubInstallationTable.connectionId + ) + ) + .where( + and( + eq( + schema.githubInstallationTable.installationId, + webhook.installationId + ), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ) + ) + ) + .limit(1) + .pipe( + Effect.mapError( + inboundDatabaseError("installation lifecycle lookup") + ) + ); + const installation = installations[0]; + if (installation === undefined) { + return; + } + const inboxId = yield* GitHubWebhookDeliveryId.generate; + const inserted = yield* db + .insert(schema.githubWebhookDeliveryTable) + .values({ + id: inboxId, + connectionId: installation.connectionId, + deliveryId: webhook.deliveryId, + eventName: "installation", + }) + .onConflictDoNothing() + .returning({ id: schema.githubWebhookDeliveryTable.id }) + .pipe( + Effect.mapError(inboundDatabaseError("lifecycle inbox record")) + ); + if (inserted.length === 0) { + return; + } + if (webhook.action === "deleted") { + yield* db + .update(schema.integrationConnectionTable) + .set({ lifecycle: "archived", archivedAt: new Date() }) + .where( + eq( + schema.integrationConnectionTable.id, + installation.connectionId + ) + ) + .pipe( + Effect.mapError(inboundDatabaseError("installation archive")) + ); + return; + } + if (webhook.action === "suspend") { + yield* db + .update(schema.githubInstallationTable) + .set({ suspendedAt: new Date() }) + .where( + eq( + schema.githubInstallationTable.connectionId, + installation.connectionId + ) + ) + .pipe( + Effect.mapError( + inboundDatabaseError("installation suspension") + ) + ); + yield* db + .update(schema.integrationConnectionTable) + .set({ lifecycle: "paused" }) + .where( + eq( + schema.integrationConnectionTable.id, + installation.connectionId + ) + ) + .pipe( + Effect.mapError(inboundDatabaseError("connection pause")) + ); + return; + } + if (webhook.action !== "unsuspend") { + return; + } + yield* db + .update(schema.githubInstallationTable) + .set({ suspendedAt: null }) + .where( + eq( + schema.githubInstallationTable.connectionId, + installation.connectionId + ) + ) + .pipe( + Effect.mapError( + inboundDatabaseError("installation restoration") + ) + ); + if ( + installation.lifecycle === "paused" && + installation.suspendedAt !== null + ) { + yield* db + .update(schema.integrationConnectionTable) + .set({ lifecycle: "active" }) + .where( + eq( + schema.integrationConnectionTable.id, + installation.connectionId + ) + ) + .pipe( + Effect.mapError( + inboundDatabaseError("connection restoration") + ) + ); + } + }) + ) + .pipe( + Effect.mapError((error) => + Schema.is(NotFoundError)(error) + ? error + : inboundDatabaseError("installation lifecycle transaction")() + ) + ), + }); +}); + +/** Live inbound service layer requiring the database and optional notification service. */ +export const GitHubInboundServiceLive = Layer.effect( + GitHubInboundService, + makeGitHubInboundService +); diff --git a/packages/domain/src/integration/github/inbound-service.ts b/packages/domain/src/integration/github/inbound-service.ts new file mode 100644 index 00000000..e80a7bfe --- /dev/null +++ b/packages/domain/src/integration/github/inbound-service.ts @@ -0,0 +1,39 @@ +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type { GitHubIntegrationError } from "./errors"; + +/** Parsed GitHub issue webhook fact accepted after signature verification by the adapter. */ +export interface GitHubIssueWebhook { + readonly deliveryId: string; + readonly eventName: "issues"; + /** GitHub App installation identity decoded from the verified webhook payload. */ + readonly installationId: string; + readonly issueNumber: number; + readonly issueState: "open" | "closed"; + readonly repositoryName: string; + readonly repositoryOwner: string; +} + +/** GitHub App installation lifecycle fact decoded after signature verification. */ +export interface GitHubInstallationLifecycleWebhook { + readonly action: "deleted" | "suspend" | "unsuspend"; + readonly deliveryId: string; + readonly installationId: string; +} + +/** Applies GitHub webhook state once using the durable inbox record before evaluating linked-issue rules. */ +export interface GitHubInboundServiceShape { + /** Applies GitHub App suspension, restoration, or removal to the linked Feeblo connection. */ + readonly applyInstallationLifecycleWebhook: ( + webhook: GitHubInstallationLifecycleWebhook + ) => Effect.Effect; + readonly applyIssueWebhook: ( + webhook: GitHubIssueWebhook + ) => Effect.Effect; +} + +/** Application inbound service implemented against the webhook inbox and post-status workflow. */ +export class GitHubInboundService extends Context.Service< + GitHubInboundService, + GitHubInboundServiceShape +>()("@feeblo/GitHubInboundService") {} diff --git a/packages/domain/src/integration/github/index.ts b/packages/domain/src/integration/github/index.ts new file mode 100644 index 00000000..971abfb9 --- /dev/null +++ b/packages/domain/src/integration/github/index.ts @@ -0,0 +1,5 @@ +/** biome-ignore-all lint/performance/noBarrelFile: GitHub module public entry point */ +/** GitHub integration application contracts and pure rule evaluation. */ +export * from "./management-service"; +export * from "./rule-evaluation"; +export * from "./schema"; diff --git a/packages/domain/src/integration/github/management-live.test.ts b/packages/domain/src/integration/github/management-live.test.ts new file mode 100644 index 00000000..ed3675fa --- /dev/null +++ b/packages/domain/src/integration/github/management-live.test.ts @@ -0,0 +1,623 @@ +import { describe, expect, layer } from "@effect/vitest"; +import { currentDb, Database, schema } from "@feeblo/db"; +import { IntegrationProviderKey } from "@feeblo/db/validation-schema/integration"; +import { + BoardId, + GitHubSyncRuleId, + IntegrationConnectionId, + type LegidOf, + PostId, + PostStatusId, + WorkspaceId, +} from "@feeblo/id"; +import { and, eq } from "drizzle-orm"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import { EmailOutboxConfig } from "../../email-outbox/config"; +import { BadRequestError, InternalServerError } from "../../rpc-errors"; +import { ExternalResourceServiceLive } from "../external-resource/live"; +import { GitHubIntegrationConfig } from "./config"; +import { GitHubProvider, type GitHubProviderShape } from "./github-provider"; +import { GitHubManagementServiceLive } from "./management-live"; +import { GitHubManagementService } from "./management-service"; +import type { GitHubResolvedIssue } from "./schema"; + +/** Recording fake provider; the test sets `createIssue` behavior per scenario. */ +const makeFakeGitHubProvider = () => { + const calls: string[] = []; + let createIssueImpl: GitHubProviderShape["createIssue"] = () => + Effect.die("createIssue not configured"); + return { + calls, + reset: () => { + calls.length = 0; + }, + setCreateIssue: (impl: GitHubProviderShape["createIssue"]) => { + createIssueImpl = impl; + }, + service: GitHubProvider.of({ + completeInstallation: () => Effect.die("unused"), + createIssue: (input) => { + calls.push("createIssue"); + return createIssueImpl(input); + }, + listRepositories: () => Effect.die("unused"), + resolveIssue: () => Effect.die("unused"), + startInstallation: () => Effect.die("unused"), + uninstallInstallation: () => Effect.die("unused"), + }), + }; +}; + +const testConfig = Layer.succeed( + GitHubIntegrationConfig, + GitHubIntegrationConfig.of({ + clientId: "client-id", + configured: true, + }) +); + +const makeTestLayer = () => { + const provider = makeFakeGitHubProvider(); + return { + provider, + layer: Layer.mergeAll( + GitHubManagementServiceLive.pipe( + Layer.provide(Layer.succeed(GitHubProvider, provider.service)), + Layer.provide(testConfig), + Layer.provide( + EmailOutboxConfig.layerTest(new URL("https://feeblo.example")) + ), + Layer.provide(ExternalResourceServiceLive), + Layer.provide(Database.PgliteDatabaseLive) + ), + Database.PgliteDatabaseLive + ), + }; +}; + +const seedPostWithConnection = Effect.gen(function* () { + const db = yield* currentDb; + const now = new Date(); + const organizationId = yield* WorkspaceId.generate; + const boardId = yield* BoardId.generate; + const postStatusId = yield* PostStatusId.generate; + const postId = yield* PostId.generate; + const connectionId = yield* IntegrationConnectionId.generate; + + yield* db.insert(schema.organizationTable).values({ + id: organizationId, + name: "GitHub management test", + slug: organizationId, + createdAt: now, + }); + yield* db.insert(schema.boardTable).values({ + id: boardId, + organizationId, + name: "Feedback", + slug: "feedback", + visibility: "PRIVATE", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.postStatusTable).values({ + id: postStatusId, + organizationId, + type: "PENDING", + orderIndex: 0, + }); + yield* db.insert(schema.postTable).values({ + id: postId, + organizationId, + boardId, + statusId: postStatusId, + title: "Dark mode", + slug: "dark-mode", + content: "Content", + createdAt: now, + updatedAt: now, + }); + yield* db.insert(schema.integrationConnectionTable).values({ + id: connectionId, + organizationId, + provider: IntegrationProviderKey.make("github"), + name: "GitHub", + lifecycle: "active", + }); + + return { organizationId, postId, connectionId }; +}); + +const seedRuleFixtures = Effect.gen(function* () { + const db = yield* currentDb; + const now = new Date(); + const organizationId = yield* WorkspaceId.generate; + const connectionId = yield* IntegrationConnectionId.generate; + const postStatusId = yield* PostStatusId.generate; + + yield* db.insert(schema.organizationTable).values({ + id: organizationId, + name: "GitHub rule test", + slug: organizationId, + createdAt: now, + }); + yield* db.insert(schema.postStatusTable).values({ + id: postStatusId, + organizationId, + type: "PENDING", + orderIndex: 0, + }); + yield* db.insert(schema.integrationConnectionTable).values({ + id: connectionId, + organizationId, + provider: IntegrationProviderKey.make("github"), + name: "GitHub", + lifecycle: "active", + }); + + return { organizationId, connectionId, postStatusId }; +}); + +const ruleCreateInput = ({ + organizationId, + connectionId, + postStatusId, +}: { + readonly organizationId: LegidOf<"WorkspaceId">; + readonly connectionId: LegidOf<"IntegrationConnectionId">; + readonly postStatusId: LegidOf<"PostStatusId">; +}) => ({ + organizationId, + connectionId, + issueMatchMode: "all" as const, + issueState: "closed" as const, + postStatusId, + upvoterNotificationPolicy: "notify_upvoters" as const, + enabled: true, +}); + +const createInput = ({ + organizationId, + postId, + connectionId, + idempotencyKey = "issue-1", +}: { + readonly organizationId: LegidOf<"WorkspaceId">; + readonly postId: LegidOf<"PostId">; + readonly connectionId: LegidOf<"IntegrationConnectionId">; + readonly idempotencyKey?: string; +}) => ({ + organizationId, + postId, + connectionId, + repositoryOwner: "acme", + repositoryName: "feedback", + idempotencyKey, +}); + +const succeedWithIssue = (input: { + readonly connectionId: LegidOf<"IntegrationConnectionId">; + readonly repositoryOwner: string; + readonly repositoryName: string; +}): Effect.Effect => + Effect.succeed({ + connectionId: input.connectionId, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + issueNumber: 7, + remoteId: "I_7", + issueUrl: new URL("https://github.com/acme/feedback/issues/7"), + issueState: "open", + title: "Dark mode", + }); + +describe("GitHub management service", () => { + const test = makeTestLayer(); + layer(test.layer)("createPostIssue idempotency", (it) => { + it.effect( + "records the created issue and marks the reservation succeeded", + () => + Effect.gen(function* () { + const db = yield* currentDb; + const service = yield* GitHubManagementService; + const seeded = yield* seedPostWithConnection; + test.provider.reset(); + test.provider.setCreateIssue((input) => + succeedWithIssue({ + connectionId: input.connectionId, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + }) + ); + + const link = yield* service.createPostIssue(createInput(seeded)); + + expect(link.displayKey).toBe("acme/feedback#7"); + const [request] = yield* db + .select({ state: schema.externalResourceCreateRequestTable.state }) + .from(schema.externalResourceCreateRequestTable) + .where( + and( + eq( + schema.externalResourceCreateRequestTable.idempotencyKey, + "issue-1" + ), + eq( + schema.externalResourceCreateRequestTable.connectionId, + seeded.connectionId + ) + ) + ); + expect(request?.state).toBe("succeeded"); + + const [resource] = yield* db + .select({ + id: schema.integrationExternalResourceTable.id, + remoteId: schema.integrationExternalResourceTable.remoteId, + remoteUrl: schema.integrationExternalResourceTable.remoteUrl, + }) + .from(schema.integrationExternalResourceTable) + .where( + and( + eq( + schema.integrationExternalResourceTable.connectionId, + seeded.connectionId + ), + eq(schema.integrationExternalResourceTable.remoteId, "I_7") + ) + ); + expect(resource?.remoteId).toBe("I_7"); + expect(resource?.remoteUrl).toBe( + "https://github.com/acme/feedback/issues/7" + ); + + const [postLink] = yield* db + .select({ + externalResourceId: + schema.postExternalResourceLinkTable.externalResourceId, + postId: schema.postExternalResourceLinkTable.postId, + }) + .from(schema.postExternalResourceLinkTable) + .where( + and( + eq( + schema.postExternalResourceLinkTable.externalResourceId, + resource?.id ?? "missing" + ), + eq(schema.postExternalResourceLinkTable.postId, seeded.postId) + ) + ); + expect(postLink?.postId).toBe(seeded.postId); + expect(postLink?.externalResourceId).toBe(resource?.id); + }) + ); + + it.effect( + "retains the reservation when issue creation is indeterminate", + () => + Effect.gen(function* () { + const db = yield* currentDb; + const service = yield* GitHubManagementService; + const seeded = yield* seedPostWithConnection; + test.provider.reset(); + test.provider.setCreateIssue(() => + Effect.fail( + new InternalServerError({ + message: "GitHub App issue creation failed.", + }) + ) + ); + + const first = yield* Effect.exit( + service.createPostIssue(createInput(seeded)) + ); + expect(Exit.isFailure(first)).toBe(true); + + const [request] = yield* db + .select({ state: schema.externalResourceCreateRequestTable.state }) + .from(schema.externalResourceCreateRequestTable) + .where( + and( + eq( + schema.externalResourceCreateRequestTable.idempotencyKey, + "issue-1" + ), + eq( + schema.externalResourceCreateRequestTable.connectionId, + seeded.connectionId + ) + ) + ); + expect(request?.state).toBe("pending"); + + const second = yield* Effect.exit( + service.createPostIssue(createInput(seeded)) + ); + expect(Exit.isFailure(second)).toBe(true); + expect( + test.provider.calls.filter((call) => call === "createIssue") + ).toHaveLength(1); + }) + ); + + it.effect( + "releases the reservation when the provider definitely rejected creation", + () => + Effect.gen(function* () { + const db = yield* currentDb; + const service = yield* GitHubManagementService; + const seeded = yield* seedPostWithConnection; + test.provider.reset(); + test.provider.setCreateIssue(() => + Effect.fail( + new BadRequestError({ + message: "GitHub rejected issue creation.", + }) + ) + ); + + const first = yield* Effect.exit( + service.createPostIssue(createInput(seeded)) + ); + expect(Exit.isFailure(first)).toBe(true); + + const [request] = yield* db + .select({ state: schema.externalResourceCreateRequestTable.state }) + .from(schema.externalResourceCreateRequestTable) + .where( + and( + eq( + schema.externalResourceCreateRequestTable.idempotencyKey, + "issue-1" + ), + eq( + schema.externalResourceCreateRequestTable.connectionId, + seeded.connectionId + ) + ) + ); + expect(request?.state).toBe("failed"); + + const second = yield* Effect.exit( + service.createPostIssue(createInput(seeded)) + ); + expect(Exit.isFailure(second)).toBe(true); + expect( + test.provider.calls.filter((call) => call === "createIssue") + ).toHaveLength(2); + }) + ); + }); + + layer(test.layer)("rule builder", (it) => { + it.effect("creates a rule and lists it for its connection", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + + const created = yield* service.createRule(ruleCreateInput(seeded)); + const rules = yield* service.listRules({ + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + }); + + expect(rules).toHaveLength(1); + expect(rules[0]?.id).toBe(created.id); + expect(rules[0]?.postStatusId).toBe(seeded.postStatusId); + expect(rules[0]?.issueMatchMode).toBe("all"); + expect(rules[0]?.issueState).toBe("closed"); + }) + ); + + it.effect("lists no rules for a connection with none configured", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + + const rules = yield* service.listRules({ + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + }); + + expect(rules).toHaveLength(0); + }) + ); + + it.effect( + "updates a rule's status, notification policy, and enabled flag without changing its shape", + () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + const created = yield* service.createRule(ruleCreateInput(seeded)); // (all, closed) + + const updated = yield* service.updateRule({ + id: created.id, + organizationId: seeded.organizationId, + connectionId: created.connectionId, + postStatusId: created.postStatusId, + upvoterNotificationPolicy: "do_not_notify_upvoters", + enabled: false, + }); + const [persisted] = yield* service.listRules({ + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + }); + + expect(updated.issueMatchMode).toBe("all"); + expect(updated.issueState).toBe("closed"); + expect(updated.enabled).toBe(false); + expect(persisted?.issueMatchMode).toBe("all"); + expect(persisted?.issueState).toBe("closed"); + expect(persisted?.enabled).toBe(false); + expect(persisted?.upvoterNotificationPolicy).toBe( + "do_not_notify_upvoters" + ); + }) + ); + + it.effect("rejects a rule shape that is not hard-wired", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + + const result = yield* service + .createRule({ + ...ruleCreateInput(seeded), + issueMatchMode: "any", + issueState: "closed", + }) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(result).toBe("BadRequestError"); + }) + ); + + it.effect("rejects a duplicate rule for the same issue states", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + yield* service.createRule(ruleCreateInput(seeded)); // (all, closed) + + const result = yield* service.createRule(ruleCreateInput(seeded)).pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(result).toBe("BadRequestError"); + }) + ); + + it.effect("allows the any-open plus all-closed rule pair", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + yield* service.createRule(ruleCreateInput(seeded)); // (all, closed) + + yield* service.createRule({ + ...ruleCreateInput(seeded), + issueMatchMode: "any", + issueState: "open", + }); + const rules = yield* service.listRules({ + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + }); + + expect(rules).toHaveLength(2); + }) + ); + + it.effect("deletes a rule so it no longer lists", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + const created = yield* service.createRule(ruleCreateInput(seeded)); + + yield* service.deleteRule({ + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + id: created.id, + }); + const rules = yield* service.listRules({ + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + }); + + expect(rules).toHaveLength(0); + }) + ); + + it.effect("rejects rule creation for a connection that is not active", () => + Effect.gen(function* () { + const service = yield* GitHubManagementService; + const seeded = yield* seedRuleFixtures; + const missingConnectionId = yield* IntegrationConnectionId.generate; + + const result = yield* service + .createRule( + ruleCreateInput({ ...seeded, connectionId: missingConnectionId }) + ) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(result).toBe("NotFoundError"); + }) + ); + + it.effect("rejects a non-hard-wired rule shape at the database level", () => + Effect.gen(function* () { + const db = yield* currentDb; + const seeded = yield* seedRuleFixtures; + const ruleId = yield* GitHubSyncRuleId.generate; + + const result = yield* db + .insert(schema.githubSyncRuleTable) + .values({ + id: ruleId, + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + issueMatchMode: "all", + issueState: "open", + postStatusId: seeded.postStatusId, + upvoterNotificationPolicy: "notify_upvoters", + enabled: true, + }) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(result).toBe("EffectDrizzleQueryError"); + }) + ); + + it.effect("rejects a duplicate rule shape at the database level", () => + Effect.gen(function* () { + const db = yield* currentDb; + const seeded = yield* seedRuleFixtures; + const firstId = yield* GitHubSyncRuleId.generate; + const secondId = yield* GitHubSyncRuleId.generate; + const ruleRow = { + organizationId: seeded.organizationId, + connectionId: seeded.connectionId, + issueMatchMode: "all" as const, + issueState: "closed" as const, + postStatusId: seeded.postStatusId, + upvoterNotificationPolicy: "notify_upvoters" as const, + enabled: true, + }; + + yield* db.insert(schema.githubSyncRuleTable).values({ + id: firstId, + ...ruleRow, + }); + const result = yield* db + .insert(schema.githubSyncRuleTable) + .values({ id: secondId, ...ruleRow }) + .pipe( + Effect.match({ + onFailure: (error) => error._tag, + onSuccess: () => "success", + }) + ); + + expect(result).toBe("EffectDrizzleQueryError"); + }) + ); + }); +}); diff --git a/packages/domain/src/integration/github/management-live.ts b/packages/domain/src/integration/github/management-live.ts new file mode 100644 index 00000000..8acfe770 --- /dev/null +++ b/packages/domain/src/integration/github/management-live.ts @@ -0,0 +1,715 @@ +import { currentDb, schema } from "@feeblo/db"; +import { isGitHubSyncRuleCombination } from "@feeblo/db/validation-schema/github-integration"; +import { IntegrationProviderKey } from "@feeblo/db/validation-schema/integration"; +import { + asLegid, + GitHubSyncRuleId, + IntegrationConnectionId, + IntegrationRouteId, + PostStatusId, +} from "@feeblo/id"; +import { makeGitHubIssueExternalResourceDraft } from "@feeblo/integration-github"; +import { githubIssueCreateCapabilityKey } from "@feeblo/integration-github/manifest"; +import { and, eq, ne } from "drizzle-orm"; +import * as Cause from "effect/Cause"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import * as Schema from "effect/Schema"; +import { EmailOutboxConfig } from "../../email-outbox/config"; +import { + BadRequestError, + InternalServerError, + NotFoundError, + UnauthorizedError, +} from "../../rpc-errors"; +import type { + ExternalResourceRecord, + PostExternalResourceLink, + RecordedPostExternalResourceLink, + RecordPostExternalResourceLink, +} from "../external-resource/schema"; +import { ExternalResourceService } from "../external-resource/service"; +import { GitHubIntegrationConfig } from "./config"; +import { GitHubProvider } from "./github-provider"; +import { + GitHubManagementService, + type GitHubManagementServiceShape, +} from "./management-service"; +import { GitHubIssueCreateRouteConfiguration } from "./schema"; + +const databaseError = (operation: string) => () => + new InternalServerError({ + message: `GitHub integration database ${operation} failed.`, + }); + +/** Failures that prove GitHub never created the issue, so the reservation can be released. */ +const isDefinitelyNonApplied = (error: unknown): boolean => + Schema.is(UnauthorizedError)(error) || + Schema.is(NotFoundError)(error) || + Schema.is(BadRequestError)(error); + +const shouldReleaseCreation = (cause: Cause.Cause): boolean => + Option.match(Cause.findErrorOption(cause), { + onNone: () => false, + onSome: (error) => isDefinitelyNonApplied(error), + }); + +/** Database-backed GitHub management service. GitHubProvider owns App installation and GitHub API I/O. */ +const makeGitHubManagementService = Effect.gen(function* () { + const db = yield* currentDb; + const provider = yield* GitHubProvider; + const config = yield* GitHubIntegrationConfig; + const emailConfig = yield* EmailOutboxConfig; + const externalResources = yield* ExternalResourceService; + const requireConnection = (organizationId: string, connectionId: string) => + db + .select() + .from(schema.integrationConnectionTable) + .where( + and( + eq(schema.integrationConnectionTable.organizationId, organizationId), + eq(schema.integrationConnectionTable.id, connectionId), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ), + eq(schema.integrationConnectionTable.lifecycle, "active") + ) + ) + .limit(1) + .pipe( + Effect.mapError(databaseError("connection lookup")), + Effect.flatMap((rows) => + rows[0] === undefined + ? new NotFoundError({ + message: "Active GitHub integration connection was not found.", + }) + : Effect.succeed(rows[0]) + ) + ); + const lockConnection = (organizationId: string, connectionId: string) => + db + .select() + .from(schema.integrationConnectionTable) + .where( + and( + eq(schema.integrationConnectionTable.organizationId, organizationId), + eq(schema.integrationConnectionTable.id, connectionId), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ) + ) + ) + .limit(1) + .for("update"); + const loadCanonicalPostUrl = (organizationId: string, postId: string) => + db + .select({ + postSlug: schema.postTable.slug, + postTitle: schema.postTable.title, + postContent: schema.postTable.content, + boardSlug: schema.boardTable.slug, + }) + .from(schema.postTable) + .innerJoin( + schema.boardTable, + eq(schema.boardTable.id, schema.postTable.boardId) + ) + .where( + and( + eq(schema.postTable.id, postId), + eq(schema.postTable.organizationId, organizationId) + ) + ) + .limit(1) + .pipe( + Effect.mapError(databaseError("post URL lookup")), + Effect.flatMap((rows) => + rows[0] === undefined + ? new NotFoundError({ + message: "Feeblo post for GitHub issue link was not found.", + }) + : Effect.succeed({ + postUrl: new URL( + `/${encodeURIComponent(organizationId)}/post/${encodeURIComponent(rows[0].boardSlug)}/${encodeURIComponent(rows[0].postSlug)}`, + emailConfig.appUrl + ), + postDescription: rows[0].postContent, + postTitle: rows[0].postTitle, + }) + ) + ); + const recordGitHubIssueExternalResource = (input: { + readonly issue: import("./schema").GitHubResolvedIssue; + readonly organizationId: ExternalResourceRecord["organizationId"]; + readonly postId: RecordPostExternalResourceLink["postId"]; + }): Effect.Effect< + { + readonly link: PostExternalResourceLink; + readonly externalResourceId: RecordedPostExternalResourceLink["externalResourceId"]; + }, + InternalServerError + > => + Effect.gen(function* () { + // One shared provider mapping keeps displayKey, safeMetadata, and title + // identical to what the delivery worker records for automatic issues. + const draft = makeGitHubIssueExternalResourceDraft({ + issueNumber: input.issue.issueNumber, + postId: input.postId, + repositoryName: input.issue.repositoryName, + repositoryOwner: input.issue.repositoryOwner, + remoteId: input.issue.remoteId, + remoteUrl: input.issue.issueUrl, + state: input.issue.issueState, + title: input.issue.title, + }); + const resource: ExternalResourceRecord = { + connectionId: input.issue.connectionId, + displayKey: draft.displayKey ?? null, + organizationId: input.organizationId, + remoteId: draft.remoteId, + remoteUrl: draft.remoteUrl, + resourceType: draft.resourceType, + safeMetadata: draft.safeMetadata, + stateKey: draft.stateKey ?? null, + title: draft.title ?? null, + }; + const recorded = yield* externalResources.recordPostLink({ + postId: input.postId, + resource, + }); + const links = yield* externalResources.listPostLinks({ + organizationId: input.organizationId, + postId: input.postId, + }); + const link = links.find( + (item) => item.id === recorded.postExternalResourceLinkId + ); + if (link === undefined) { + return yield* new InternalServerError({ + message: + "GitHub external resource link was not found after recording.", + }); + } + return { externalResourceId: recorded.externalResourceId, link }; + }); + const service: GitHubManagementServiceShape = { + status: () => Effect.succeed({ configured: config.configured }), + connectStart: (input) => provider.startInstallation(input.organizationId), + connectComplete: (input) => provider.completeInstallation(input), + disconnect: (input) => + Effect.gen(function* () { + const disconnecting = yield* db + .transaction(() => + Effect.gen(function* () { + const [connection] = yield* lockConnection( + input.organizationId, + input.connectionId + ).pipe(Effect.mapError(databaseError("connection lookup"))); + if (connection === undefined) { + return yield* new NotFoundError({ + message: "GitHub integration connection was not found.", + }); + } + if (connection.lifecycle === "archived") { + return undefined; + } + if (connection.lifecycle === "connecting") { + return yield* new NotFoundError({ + message: "GitHub integration connection was not found.", + }); + } + const now = new Date(); + yield* db + .update(schema.integrationConnectionTable) + .set({ lifecycle: "disconnecting", updatedAt: now }) + .where( + and( + eq( + schema.integrationConnectionTable.id, + input.connectionId + ), + eq( + schema.integrationConnectionTable.organizationId, + input.organizationId + ), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ) + ) + ) + .pipe( + Effect.mapError( + databaseError("connection disconnecting update") + ) + ); + yield* db + .update(schema.integrationRouteTable) + .set({ enabled: false, updatedAt: now }) + .where( + and( + eq( + schema.integrationRouteTable.connectionId, + input.connectionId + ), + eq( + schema.integrationRouteTable.organizationId, + input.organizationId + ) + ) + ) + .pipe(Effect.mapError(databaseError("route disable"))); + yield* db + .update(schema.integrationDeliveryTable) + .set({ canceledAt: now, state: "canceled", updatedAt: now }) + .where( + and( + eq( + schema.integrationDeliveryTable.connectionId, + input.connectionId + ), + eq( + schema.integrationDeliveryTable.organizationId, + input.organizationId + ), + eq(schema.integrationDeliveryTable.state, "pending") + ) + ) + .pipe(Effect.mapError(databaseError("delivery cancellation"))); + return connection; + }) + ) + .pipe(Effect.mapError(databaseError("disconnect transaction"))); + + if (disconnecting === undefined) { + return; + } + + const uninstall = yield* Effect.exit( + provider.uninstallInstallation({ + connectionId: input.connectionId, + }) + ); + if (Exit.isFailure(uninstall)) { + yield* db + .update(schema.integrationConnectionTable) + .set({ lifecycle: "revocation_unconfirmed", updatedAt: new Date() }) + .where(eq(schema.integrationConnectionTable.id, input.connectionId)) + .pipe(Effect.mapError(databaseError("disconnect state update"))); + return yield* Effect.failCause(uninstall.cause); + } + + const archivedAt = new Date(); + yield* db + .update(schema.integrationConnectionTable) + .set({ + archivedAt, + credentialsCiphertext: null, + lifecycle: "archived", + updatedAt: archivedAt, + }) + .where( + and( + eq(schema.integrationConnectionTable.id, input.connectionId), + eq( + schema.integrationConnectionTable.organizationId, + input.organizationId + ), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ) + ) + ) + .pipe(Effect.mapError(databaseError("connection removal"))); + }), + listConnections: ({ organizationId }) => + db + .select({ + id: schema.integrationConnectionTable.id, + login: schema.githubInstallationTable.accountLogin, + lifecycle: schema.integrationConnectionTable.lifecycle, + createdAt: schema.integrationConnectionTable.createdAt, + }) + .from(schema.integrationConnectionTable) + .innerJoin( + schema.githubInstallationTable, + eq( + schema.githubInstallationTable.connectionId, + schema.integrationConnectionTable.id + ) + ) + .where( + and( + eq( + schema.integrationConnectionTable.organizationId, + organizationId + ), + eq( + schema.integrationConnectionTable.provider, + IntegrationProviderKey.make("github") + ), + ne(schema.integrationConnectionTable.lifecycle, "archived") + ) + ) + .pipe( + Effect.map((rows) => + rows.map((row) => ({ + ...row, + id: asLegid(IntegrationConnectionId)(row.id), + })) + ), + Effect.mapError(databaseError("connection list")) + ), + listRepositories: (input) => + requireConnection(input.organizationId, input.connectionId).pipe( + Effect.flatMap(() => + provider.listRepositories({ connectionId: input.connectionId }) + ) + ), + getSettings: (input) => + requireConnection(input.organizationId, input.connectionId).pipe( + Effect.flatMap(() => + db + .select({ + config: schema.integrationRouteTable.providerConfig, + enabled: schema.integrationRouteTable.enabled, + }) + .from(schema.integrationRouteTable) + .where( + and( + eq( + schema.integrationRouteTable.connectionId, + input.connectionId + ), + eq( + schema.integrationRouteTable.capabilityKey, + githubIssueCreateCapabilityKey + ) + ) + ) + .limit(1) + ), + Effect.mapError(databaseError("settings read")), + Effect.map((rows) => { + const row = rows[0]; + const config = Schema.decodeUnknownOption( + GitHubIssueCreateRouteConfiguration + )(row?.config).pipe( + Option.getOrElse(() => + GitHubIssueCreateRouteConfiguration.make({ version: 1 }) + ) + ); + return { + enabled: row?.enabled ?? false, + boardScope: + config.boardId === undefined + ? ("any_board" as const) + : ("specific_board" as const), + boardId: config.boardId ?? null, + repositoryOwner: config.repositoryOwner ?? null, + repositoryName: config.repositoryName ?? null, + }; + }) + ), + updateSettings: (input) => + Effect.gen(function* () { + yield* requireConnection(input.organizationId, input.connectionId); + if ( + input.enabled && + (input.repositoryOwner === null || input.repositoryName === null) + ) { + return yield* new BadRequestError({ + message: + "GitHub integration enabled settings require a repository.", + }); + } + if (input.boardScope === "specific_board" && input.boardId === null) { + return yield* new BadRequestError({ + message: + "GitHub integration specific board settings require a board.", + }); + } + if ( + input.enabled && + input.repositoryOwner !== null && + input.repositoryName !== null + ) { + const repositories = yield* provider.listRepositories({ + connectionId: input.connectionId, + }); + const selectedRepository = repositories.some( + (repository) => + repository.owner === input.repositoryOwner && + repository.name === input.repositoryName + ); + if (!selectedRepository) { + return yield* new NotFoundError({ + message: + "Selected GitHub repository is not available to this App installation.", + }); + } + } + const routeId = yield* IntegrationRouteId.generate.pipe( + Effect.mapError(databaseError("route identifier generation")) + ); + const providerConfig = { + version: 1, + ...(input.boardId === null ? {} : { boardId: input.boardId }), + ...(input.repositoryOwner === null + ? {} + : { repositoryOwner: input.repositoryOwner }), + ...(input.repositoryName === null + ? {} + : { repositoryName: input.repositoryName }), + }; + yield* db + .insert(schema.integrationRouteTable) + .values({ + id: routeId, + organizationId: input.organizationId, + connectionId: input.connectionId, + capabilityKey: githubIssueCreateCapabilityKey, + routeKey: "", + configVersion: 1, + enabled: input.enabled, + eventTypes: ["feedback.post.created"], + providerConfig, + safeDisplayMetadata: {}, + }) + .onConflictDoUpdate({ + target: [ + schema.integrationRouteTable.connectionId, + schema.integrationRouteTable.capabilityKey, + schema.integrationRouteTable.routeKey, + ], + set: { enabled: input.enabled, providerConfig }, + }) + .pipe(Effect.mapError(databaseError("settings upsert"))); + return { + enabled: input.enabled, + boardScope: input.boardScope, + boardId: input.boardId, + repositoryOwner: input.repositoryOwner, + repositoryName: input.repositoryName, + }; + }), + listRules: (input) => + requireConnection(input.organizationId, input.connectionId).pipe( + Effect.flatMap(() => + db + .select() + .from(schema.githubSyncRuleTable) + .where( + and( + eq( + schema.githubSyncRuleTable.organizationId, + input.organizationId + ), + eq(schema.githubSyncRuleTable.connectionId, input.connectionId) + ) + ) + ), + Effect.mapError(databaseError("rule list")), + Effect.map((rows) => + rows.map((row) => ({ + id: asLegid(GitHubSyncRuleId)(row.id), + connectionId: asLegid(IntegrationConnectionId)(row.connectionId), + issueMatchMode: row.issueMatchMode, + issueState: row.issueState, + postStatusId: asLegid(PostStatusId)(row.postStatusId), + upvoterNotificationPolicy: row.upvoterNotificationPolicy, + enabled: row.enabled, + })) + ) + ), + createRule: (input) => + Effect.gen(function* () { + yield* requireConnection(input.organizationId, input.connectionId); + if ( + !isGitHubSyncRuleCombination(input.issueMatchMode, input.issueState) + ) { + return yield* new BadRequestError({ + message: + 'Only "any open" and "all closed" GitHub synchronization rules are supported.', + }); + } + const existing = yield* db + .select({ id: schema.githubSyncRuleTable.id }) + .from(schema.githubSyncRuleTable) + .where( + and( + eq( + schema.githubSyncRuleTable.organizationId, + input.organizationId + ), + eq(schema.githubSyncRuleTable.connectionId, input.connectionId), + eq( + schema.githubSyncRuleTable.issueMatchMode, + input.issueMatchMode + ), + eq(schema.githubSyncRuleTable.issueState, input.issueState) + ) + ) + .limit(1) + .pipe(Effect.mapError(databaseError("rule duplicate lookup"))); + if (existing[0] !== undefined) { + return yield* new BadRequestError({ + message: + "A GitHub synchronization rule for these issue states already exists.", + }); + } + const id = yield* GitHubSyncRuleId.generate.pipe( + Effect.mapError(databaseError("rule identifier generation")) + ); + yield* db + .insert(schema.githubSyncRuleTable) + .values({ ...input, id }) + .pipe(Effect.mapError(databaseError("rule creation"))); + return { ...input, id }; + }), + updateRule: (input) => + Effect.gen(function* () { + const rows = yield* db + .update(schema.githubSyncRuleTable) + .set({ + enabled: input.enabled, + postStatusId: input.postStatusId, + upvoterNotificationPolicy: input.upvoterNotificationPolicy, + }) + .where( + and( + eq(schema.githubSyncRuleTable.id, input.id), + eq( + schema.githubSyncRuleTable.organizationId, + input.organizationId + ), + eq(schema.githubSyncRuleTable.connectionId, input.connectionId) + ) + ) + .returning({ + connectionId: schema.githubSyncRuleTable.connectionId, + issueMatchMode: schema.githubSyncRuleTable.issueMatchMode, + issueState: schema.githubSyncRuleTable.issueState, + }) + .pipe(Effect.mapError(databaseError("rule update"))); + if (rows[0] === undefined) { + return yield* new NotFoundError({ + message: "GitHub synchronization rule was not found.", + }); + } + return { + id: input.id, + connectionId: asLegid(IntegrationConnectionId)(rows[0].connectionId), + issueMatchMode: rows[0].issueMatchMode, + issueState: rows[0].issueState, + postStatusId: input.postStatusId, + upvoterNotificationPolicy: input.upvoterNotificationPolicy, + enabled: input.enabled, + }; + }), + deleteRule: (input) => + db + .delete(schema.githubSyncRuleTable) + .where( + and( + eq(schema.githubSyncRuleTable.id, input.id), + eq(schema.githubSyncRuleTable.organizationId, input.organizationId), + eq(schema.githubSyncRuleTable.connectionId, input.connectionId) + ) + ) + .returning({ id: schema.githubSyncRuleTable.id }) + .pipe( + Effect.mapError(databaseError("rule deletion")), + Effect.flatMap((rows) => + rows[0] === undefined + ? new NotFoundError({ + message: "GitHub synchronization rule was not found.", + }) + : Effect.void + ) + ), + createPostIssue: (input) => + Effect.gen(function* () { + yield* requireConnection(input.organizationId, input.connectionId); + const request = yield* externalResources.reserveCreation({ + connectionId: input.connectionId, + idempotencyKey: input.idempotencyKey, + organizationId: input.organizationId, + postId: input.postId, + }); + if (!request.reserved) { + const links = yield* externalResources.listPostLinks({ + organizationId: input.organizationId, + postId: input.postId, + }); + const completed = links.find( + (link) => link.id === request.postExternalResourceLinkId + ); + if (completed !== undefined) { + return completed; + } + return yield* new InternalServerError({ + message: "GitHub issue creation is already pending.", + }); + } + return yield* Effect.gen(function* () { + const post = yield* loadCanonicalPostUrl( + input.organizationId, + input.postId + ); + const issue = yield* provider.createIssue({ + ...input, + postDescription: post.postDescription, + postTitle: post.postTitle, + postUrl: post.postUrl, + }); + const recorded = yield* recordGitHubIssueExternalResource({ + issue, + organizationId: input.organizationId, + postId: input.postId, + }); + yield* externalResources.completeCreation({ + externalResourceId: recorded.externalResourceId, + postExternalResourceLinkId: recorded.link.id, + requestId: request.id, + }); + return recorded.link; + }).pipe( + Effect.onErrorIf(shouldReleaseCreation, () => + externalResources + .failCreation({ requestId: request.id }) + .pipe(Effect.catch((cause) => Effect.logError(cause))) + ) + ); + }), + linkPostIssue: (input) => + Effect.gen(function* () { + yield* requireConnection(input.organizationId, input.connectionId); + const post = yield* loadCanonicalPostUrl( + input.organizationId, + input.postId + ); + const issue = yield* provider.resolveIssue({ + ...input, + postUrl: post.postUrl, + }); + return (yield* recordGitHubIssueExternalResource({ + issue, + organizationId: input.organizationId, + postId: input.postId, + })).link; + }), + }; + return GitHubManagementService.of(service); +}); + +/** Live service layer requiring the server-selected GitHub App API adapter. */ +export const GitHubManagementServiceLive = Layer.effect( + GitHubManagementService, + makeGitHubManagementService +); diff --git a/packages/domain/src/integration/github/management-service.ts b/packages/domain/src/integration/github/management-service.ts new file mode 100644 index 00000000..c93699f3 --- /dev/null +++ b/packages/domain/src/integration/github/management-service.ts @@ -0,0 +1,62 @@ +import * as Context from "effect/Context"; +import type * as Effect from "effect/Effect"; +import type { PostExternalResourceLink } from "../external-resource/schema"; +import type { GitHubIntegrationError } from "./errors"; +import type * as S from "./schema"; + +/** Application-owned GitHub capability: the provider adapter implements GitHub App installation and API I/O behind this boundary. */ +export interface GitHubManagementServiceShape { + /** Completes the App setup callback; provider code verifies temporary installer access and persists no token. */ + readonly connectComplete: ( + input: S.GitHubAppInstallationCallback + ) => Effect.Effect< + { readonly organizationId: string }, + GitHubIntegrationError + >; + readonly connectStart: ( + input: S.GitHubConnectStart + ) => Effect.Effect; + /** Creates exactly one issue per stable idempotency key, then persists its link. */ + readonly createPostIssue: ( + input: S.GitHubPostIssueCreate + ) => Effect.Effect; + readonly createRule: ( + input: S.GitHubRuleCreate + ) => Effect.Effect; + readonly deleteRule: ( + input: S.GitHubRuleDelete + ) => Effect.Effect; + /** Archives a GitHub connection and disables its routes while retaining historical links. */ + readonly disconnect: ( + input: S.GitHubConnectionDisconnect + ) => Effect.Effect; + readonly getSettings: ( + input: S.GitHubSettingsGet + ) => Effect.Effect; + /** Links one existing issue; the connection/repository/number unique key makes retries safe. */ + readonly linkPostIssue: ( + input: S.GitHubPostIssueLink + ) => Effect.Effect; + readonly listConnections: ( + input: S.GitHubConnectionList + ) => Effect.Effect; + readonly listRepositories: ( + input: S.GitHubRepositoryList + ) => Effect.Effect; + readonly listRules: ( + input: S.GitHubRuleList + ) => Effect.Effect; + readonly status: () => Effect.Effect; + readonly updateRule: ( + input: S.GitHubRuleUpdate + ) => Effect.Effect; + readonly updateSettings: ( + input: S.GitHubSettingsUpdate + ) => Effect.Effect; +} + +/** Service key supplied by server composition after it wires the GitHub adapter. */ +export class GitHubManagementService extends Context.Service< + GitHubManagementService, + GitHubManagementServiceShape +>()("@feeblo/GitHubManagementService") {} diff --git a/packages/domain/src/integration/github/oauth-callback.test.ts b/packages/domain/src/integration/github/oauth-callback.test.ts new file mode 100644 index 00000000..22bf3dd0 --- /dev/null +++ b/packages/domain/src/integration/github/oauth-callback.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; +import { parseGitHubAppInstallationCallbackUrl } from "./oauth-callback"; + +describe("parseGitHubAppInstallationCallbackUrl", () => { + it.effect("parses a complete GitHub App installation callback", () => + Effect.gen(function* () { + const callback = yield* parseGitHubAppInstallationCallbackUrl( + "/github/app/callback?code=installer-code&state=opaque-state&installation_id=123456&setup_action=install" + ); + + expect(callback).toEqual({ + code: "installer-code", + state: "opaque-state", + installationId: "123456", + setupAction: "install", + }); + }) + ); + + it.effect("rejects a callback without the installation identity", () => + Effect.gen(function* () { + const exit = yield* Effect.exit( + parseGitHubAppInstallationCallbackUrl( + "/github/app/callback?code=installer-code&state=opaque-state&setup_action=install" + ) + ); + + expect(Exit.isFailure(exit)).toBe(true); + }) + ); +}); diff --git a/packages/domain/src/integration/github/oauth-callback.ts b/packages/domain/src/integration/github/oauth-callback.ts new file mode 100644 index 00000000..772435f9 --- /dev/null +++ b/packages/domain/src/integration/github/oauth-callback.ts @@ -0,0 +1,27 @@ +import * as Effect from "effect/Effect"; +import * as Schema from "effect/Schema"; +import { BadRequestError } from "../../rpc-errors"; +import { + GitHubAppInstallationCallback, + type GitHubAppInstallationCallback as GitHubAppInstallationCallbackType, +} from "./schema"; + +/** Parses the GitHub App installation callback URL without exposing its temporary code in logs or responses. */ +export const parseGitHubAppInstallationCallbackUrl = ( + url: string +): Effect.Effect => { + const parsed = new URL(url, "http://localhost"); + return Schema.decodeUnknownEffect(GitHubAppInstallationCallback)({ + code: parsed.searchParams.get("code"), + state: parsed.searchParams.get("state"), + installationId: parsed.searchParams.get("installation_id"), + setupAction: parsed.searchParams.get("setup_action"), + }).pipe( + Effect.mapError( + () => + new BadRequestError({ + message: "GitHub App installation callback parameters are invalid.", + }) + ) + ); +}; diff --git a/packages/domain/src/integration/github/rpcs.ts b/packages/domain/src/integration/github/rpcs.ts new file mode 100644 index 00000000..13b5e392 --- /dev/null +++ b/packages/domain/src/integration/github/rpcs.ts @@ -0,0 +1,75 @@ +import * as Schema from "effect/Schema"; +import * as Rpc from "effect/unstable/rpc/Rpc"; +import * as RpcGroup from "effect/unstable/rpc/RpcGroup"; +import { AuthMiddleware } from "../../session-middleware"; +import { PostExternalResourceLink } from "../external-resource/schema"; +import { GitHubIntegrationErrors } from "./errors"; +import * as S from "./schema"; + +/** Authenticated RPC surface for GitHub connection, issue publishing, and issue-state rules. */ +export class GitHubManagementRpcs extends RpcGroup.make( + Rpc.make("GitHubIntegrationStatus", { + success: S.GitHubIntegrationStatus, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubConnectionList", { + success: Schema.Array(S.GitHubConnection), + payload: S.GitHubConnectionList, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubConnectStart", { + success: S.GitHubConnectStarted, + payload: S.GitHubConnectStart, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubConnectionDisconnect", { + success: Schema.Void, + payload: S.GitHubConnectionDisconnect, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubRepositoryList", { + success: Schema.Array(S.GitHubRepository), + payload: S.GitHubRepositoryList, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubSettingsGet", { + success: S.GitHubPublishSettings, + payload: S.GitHubSettingsGet, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubSettingsUpdate", { + success: S.GitHubPublishSettings, + payload: S.GitHubSettingsUpdate, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubRuleList", { + success: Schema.Array(S.GitHubSyncRule), + payload: S.GitHubRuleList, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubRuleCreate", { + success: S.GitHubSyncRule, + payload: S.GitHubRuleCreate, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubRuleUpdate", { + success: S.GitHubSyncRule, + payload: S.GitHubRuleUpdate, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubRuleDelete", { + success: Schema.Void, + payload: S.GitHubRuleDelete, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubPostIssueCreate", { + success: PostExternalResourceLink, + payload: S.GitHubPostIssueCreate, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware), + Rpc.make("GitHubPostIssueLink", { + success: PostExternalResourceLink, + payload: S.GitHubPostIssueLink, + error: GitHubIntegrationErrors, + }).middleware(AuthMiddleware) +) {} diff --git a/packages/domain/src/integration/github/rule-evaluation.test.ts b/packages/domain/src/integration/github/rule-evaluation.test.ts new file mode 100644 index 00000000..a9334648 --- /dev/null +++ b/packages/domain/src/integration/github/rule-evaluation.test.ts @@ -0,0 +1,43 @@ +import { + asLegid, + GitHubSyncRuleId, + IntegrationConnectionId, + PostStatusId, +} from "@feeblo/id"; +import { describe, expect, it } from "vitest"; +import { findMatchingGitHubSyncRules } from "./rule-evaluation"; + +const rule = { + id: asLegid(GitHubSyncRuleId)("gsr_test"), + connectionId: asLegid(IntegrationConnectionId)("icn_test"), + issueMatchMode: "all" as const, + issueState: "closed" as const, + postStatusId: asLegid(PostStatusId)("pss_closed"), + upvoterNotificationPolicy: "notify_upvoters" as const, + enabled: true, +}; + +describe("findMatchingGitHubSyncRules", () => { + it("requires every linked issue for an all rule", () => { + expect(findMatchingGitHubSyncRules([rule], ["closed", "open"])).toEqual([]); + expect(findMatchingGitHubSyncRules([rule], ["closed", "closed"])).toEqual([ + rule, + ]); + }); + + it("matches at least one linked issue for an any rule and ignores disabled rules", () => { + expect( + findMatchingGitHubSyncRules( + [ + { ...rule, issueMatchMode: "any" }, + { ...rule, enabled: false }, + ], + ["open", "closed"] + ) + ).toEqual([{ ...rule, issueMatchMode: "any" }]); + }); + + it("does not match a rule when a post has no linked issues", () => { + expect(findMatchingGitHubSyncRules([rule], [])).toEqual([]); + }); +}); diff --git a/packages/domain/src/integration/github/rule-evaluation.ts b/packages/domain/src/integration/github/rule-evaluation.ts new file mode 100644 index 00000000..c2371cf3 --- /dev/null +++ b/packages/domain/src/integration/github/rule-evaluation.ts @@ -0,0 +1,20 @@ +import type { TGitHubIssueState } from "@feeblo/db/validation-schema/github-integration"; +import type { GitHubSyncRule } from "./schema"; + +/** Determines which enabled rules match the current aggregate state of linked GitHub issues. */ +export const findMatchingGitHubSyncRules = ( + rules: readonly GitHubSyncRule[], + issueStates: readonly TGitHubIssueState[] +): readonly GitHubSyncRule[] => { + if (issueStates.length === 0) { + return []; + } + return rules.filter((rule) => { + if (!rule.enabled) { + return false; + } + return rule.issueMatchMode === "all" + ? issueStates.every((state) => state === rule.issueState) + : issueStates.some((state) => state === rule.issueState); + }); +}; diff --git a/packages/domain/src/integration/github/schema.ts b/packages/domain/src/integration/github/schema.ts new file mode 100644 index 00000000..1ee995bb --- /dev/null +++ b/packages/domain/src/integration/github/schema.ts @@ -0,0 +1,212 @@ +import { + GitHubIssueMatchMode, + GitHubIssueState, + GitHubPublishBoardScope, + GitHubUpvoterNotificationPolicy, +} from "@feeblo/db/validation-schema/github-integration"; +import { IntegrationConnectionLifecycleStatus } from "@feeblo/db/validation-schema/integration"; +import { + BoardId, + GitHubSyncRuleId, + IntegrationConnectionId, + PostId, + PostStatusId, + WorkspaceId, +} from "@feeblo/id"; +import { GitHubIssueCreateRouteConfiguration as IntegrationGitHubIssueCreateRouteConfiguration } from "@feeblo/integration-github/manifest"; +import * as Schema from "effect/Schema"; + +/** Safe GitHub App installation details; installation access tokens are never persisted. */ +export const GitHubConnection = Schema.Struct({ + id: IntegrationConnectionId.schema, + login: Schema.NullOr(Schema.String), + lifecycle: IntegrationConnectionLifecycleStatus, + createdAt: Schema.DateFromString, +}); +export type GitHubConnection = Schema.Schema.Type; + +/** A repository available to the authenticated GitHub integration connection. */ +export const GitHubRepository = Schema.Struct({ + owner: Schema.NonEmptyString, + name: Schema.NonEmptyString, + fullName: Schema.NonEmptyString, + private: Schema.Boolean, +}); +export type GitHubRepository = Schema.Schema.Type; + +/** Automatic issue publishing settings stored as safe route configuration. */ +export const GitHubPublishSettings = Schema.Struct({ + enabled: Schema.Boolean, + boardScope: GitHubPublishBoardScope, + boardId: Schema.NullOr(BoardId.schema), + repositoryOwner: Schema.NullOr(Schema.String), + repositoryName: Schema.NullOr(Schema.String), +}); +export type GitHubPublishSettings = Schema.Schema.Type< + typeof GitHubPublishSettings +>; + +/** + * Persisted safe GitHub issue-create route configuration; credentials never + * belong here. Field definitions are shared with the provider's manifest + * schema; the repo and board fields stay optional here so an unconfigured + * route can decode to defaults, while the provider's schema keeps them + * required for enabled deliveries. + */ +export const GitHubIssueCreateRouteConfiguration = Schema.Struct({ + ...IntegrationGitHubIssueCreateRouteConfiguration.fields, + boardId: Schema.optionalKey(BoardId.schema), + repositoryName: Schema.optionalKey(Schema.NonEmptyString), + repositoryOwner: Schema.optionalKey(Schema.NonEmptyString), +}); +export type GitHubIssueCreateRouteConfiguration = Schema.Schema.Type< + typeof GitHubIssueCreateRouteConfiguration +>; + +/** One rule that turns aggregate linked GitHub issue state into a Feeblo status. */ +export const GitHubSyncRule = Schema.Struct({ + id: GitHubSyncRuleId.schema, + connectionId: IntegrationConnectionId.schema, + issueMatchMode: GitHubIssueMatchMode, + issueState: GitHubIssueState, + postStatusId: PostStatusId.schema, + upvoterNotificationPolicy: GitHubUpvoterNotificationPolicy, + enabled: Schema.Boolean, +}); +export type GitHubSyncRule = Schema.Schema.Type; + +/** A GitHub issue normalized by the GitHub provider before generic persistence. */ +export const GitHubResolvedIssue = Schema.Struct({ + connectionId: IntegrationConnectionId.schema, + repositoryOwner: Schema.String, + repositoryName: Schema.String, + issueNumber: Schema.Int, + remoteId: Schema.NonEmptyString, + issueUrl: Schema.URLFromString, + issueState: GitHubIssueState, + /** GitHub issue title, persisted so the linked-resource card matches the provider. */ + title: Schema.String, +}); +export type GitHubResolvedIssue = Schema.Schema.Type< + typeof GitHubResolvedIssue +>; + +/** Starts the GitHub App installation flow. */ +export const GitHubConnectStart = Schema.Struct({ + organizationId: WorkspaceId.schema, +}); +export type GitHubConnectStart = Schema.Schema.Type; +export const GitHubConnectStarted = Schema.Struct({ + authorizeUrl: Schema.URLFromString, +}); +export type GitHubConnectStarted = Schema.Schema.Type< + typeof GitHubConnectStarted +>; + +/** Actions GitHub sends to the App installation setup callback. */ +export const GitHubInstallationSetupAction = Schema.Literals([ + "install", + "update", +]); +export type GitHubInstallationSetupAction = Schema.Schema.Type< + typeof GitHubInstallationSetupAction +>; + +/** Verified App installation callback parameters. The short-lived installer token is provider-private. */ +export const GitHubAppInstallationCallback = Schema.Struct({ + code: Schema.NonEmptyString, + state: Schema.NonEmptyString, + installationId: Schema.NonEmptyString, + setupAction: GitHubInstallationSetupAction, +}); +export type GitHubAppInstallationCallback = Schema.Schema.Type< + typeof GitHubAppInstallationCallback +>; +export const GitHubConnectionList = Schema.Struct({ + organizationId: WorkspaceId.schema, +}); +export type GitHubConnectionList = Schema.Schema.Type< + typeof GitHubConnectionList +>; +/** Removes one GitHub App connection from Feeblo without deleting historical issue links. */ +export const GitHubConnectionDisconnect = Schema.Struct({ + organizationId: WorkspaceId.schema, + connectionId: IntegrationConnectionId.schema, +}); +export type GitHubConnectionDisconnect = Schema.Schema.Type< + typeof GitHubConnectionDisconnect +>; +export const GitHubRepositoryList = Schema.Struct({ + organizationId: WorkspaceId.schema, + connectionId: IntegrationConnectionId.schema, +}); +export type GitHubRepositoryList = Schema.Schema.Type< + typeof GitHubRepositoryList +>; +export const GitHubSettingsGet = Schema.Struct({ + organizationId: WorkspaceId.schema, + connectionId: IntegrationConnectionId.schema, +}); +export type GitHubSettingsGet = Schema.Schema.Type; +export const GitHubSettingsUpdate = Schema.Struct({ + ...GitHubSettingsGet.fields, + ...GitHubPublishSettings.fields, +}); +export type GitHubSettingsUpdate = Schema.Schema.Type< + typeof GitHubSettingsUpdate +>; +export const GitHubRuleList = GitHubSettingsGet; +export type GitHubRuleList = Schema.Schema.Type; +export const GitHubRuleCreate = Schema.Struct({ + ...GitHubSettingsGet.fields, + issueMatchMode: GitHubIssueMatchMode, + issueState: GitHubIssueState, + postStatusId: PostStatusId.schema, + upvoterNotificationPolicy: GitHubUpvoterNotificationPolicy, + enabled: Schema.Boolean, +}); +export type GitHubRuleCreate = Schema.Schema.Type; +/** Updates the mutable fields of a hard-wired GitHub synchronization rule; the issue-state shape is fixed. */ +export const GitHubRuleUpdate = Schema.Struct({ + ...GitHubSettingsGet.fields, + id: GitHubSyncRuleId.schema, + postStatusId: PostStatusId.schema, + upvoterNotificationPolicy: GitHubUpvoterNotificationPolicy, + enabled: Schema.Boolean, +}); +export type GitHubRuleUpdate = Schema.Schema.Type; +export const GitHubRuleDelete = Schema.Struct({ + organizationId: WorkspaceId.schema, + connectionId: IntegrationConnectionId.schema, + id: GitHubSyncRuleId.schema, +}); +export type GitHubRuleDelete = Schema.Schema.Type; +export const GitHubPostIssueCreate = Schema.Struct({ + organizationId: WorkspaceId.schema, + postId: PostId.schema, + connectionId: IntegrationConnectionId.schema, + repositoryOwner: Schema.NonEmptyString, + repositoryName: Schema.NonEmptyString, + idempotencyKey: Schema.NonEmptyString, +}); +export type GitHubPostIssueCreate = Schema.Schema.Type< + typeof GitHubPostIssueCreate +>; +export const GitHubPostIssueLink = Schema.Struct({ + organizationId: WorkspaceId.schema, + postId: PostId.schema, + connectionId: IntegrationConnectionId.schema, + repositoryOwner: Schema.NonEmptyString, + repositoryName: Schema.NonEmptyString, + issueNumber: Schema.Int.check(Schema.isGreaterThan(0)), + idempotencyKey: Schema.NonEmptyString, +}); +export type GitHubPostIssueLink = Schema.Schema.Type< + typeof GitHubPostIssueLink +>; +export const GitHubIntegrationStatus = Schema.Struct({ + configured: Schema.Boolean, +}); +export type GitHubIntegrationStatus = Schema.Schema.Type< + typeof GitHubIntegrationStatus +>; diff --git a/packages/domain/src/integration/post-event-recording.ts b/packages/domain/src/integration/post-event-recording.ts index 40ab8f7e..69caf034 100644 --- a/packages/domain/src/integration/post-event-recording.ts +++ b/packages/domain/src/integration/post-event-recording.ts @@ -9,7 +9,7 @@ import { EmailOutboxConfig } from "../email-outbox/config"; import { PostRepository } from "../post/repository"; /** Failure while assembling or recording a post integration event. */ -export class PostIntegrationEventRecordingError extends Schema.TaggedErrorClass()( +export class PostIntegrationEventRecordingError extends Schema.TaggedError()( "PostIntegrationEventRecordingError", { kind: Schema.Literals(["infrastructure", "lookup", "recording"]), @@ -30,6 +30,8 @@ export type PostIntegrationEventActor = export interface PostIntegrationEventInput { readonly actor: PostIntegrationEventActor; readonly boardId: LegidOf<"BoardId">; + /** Post body (sanitized markdown) carried only for post-created events. */ + readonly description?: string; readonly eventType: "feedback.post.created" | "feedback.post.status_changed"; readonly metadata?: Readonly>; readonly organizationId: LegidOf<"WorkspaceId">; @@ -114,6 +116,10 @@ export const recordPostIntegrationEvent = Effect.fn( board, post: { id: input.postId, + ...(input.description === undefined || + input.description.length === 0 + ? {} + : { description: input.description }), ...(input.metadata !== undefined && Object.keys(input.metadata).length > 0 ? { metadata: { ...input.metadata } } diff --git a/packages/domain/src/integration/slack/slack-channel-service.ts b/packages/domain/src/integration/slack/slack-channel-service.ts index d44c915e..b302d99a 100644 --- a/packages/domain/src/integration/slack/slack-channel-service.ts +++ b/packages/domain/src/integration/slack/slack-channel-service.ts @@ -4,7 +4,10 @@ import { makeSlackApiClient, type SlackApiClient, } from "@feeblo/integration-slack"; -import { SlackChannelNotificationRouteConfiguration } from "@feeblo/integration-slack/manifest"; +import { + SlackChannelNotificationRouteConfiguration, + slackChannelNotificationsCapabilityKey, +} from "@feeblo/integration-slack/manifest"; import { and, eq } from "drizzle-orm"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; @@ -107,7 +110,7 @@ export const makeSlackChannelServiceLive = ( ), eq( schema.integrationRouteTable.capabilityKey, - "channel.notifications" + slackChannelNotificationsCapabilityKey ), eq(schema.integrationRouteTable.enabled, true) ) @@ -189,7 +192,7 @@ export const makeSlackChannelServiceLive = ( ), eq( schema.integrationRouteTable.capabilityKey, - "channel.notifications" + slackChannelNotificationsCapabilityKey ), eq( schema.integrationRouteTable.organizationId, @@ -207,7 +210,7 @@ export const makeSlackChannelServiceLive = ( if (input.enabled) { if (route === undefined) { yield* db.insert(schema.integrationRouteTable).values({ - capabilityKey: "channel.notifications", + capabilityKey: slackChannelNotificationsCapabilityKey, configVersion: 1, connectionId: input.connectionId, enabled: true, diff --git a/packages/domain/src/integration/slack/slack-connection-service.ts b/packages/domain/src/integration/slack/slack-connection-service.ts index 70670dfb..43ad1f9b 100644 --- a/packages/domain/src/integration/slack/slack-connection-service.ts +++ b/packages/domain/src/integration/slack/slack-connection-service.ts @@ -14,7 +14,11 @@ import { decryptSlackCredentialMaterial, encryptSlackCredentialMaterial, } from "@feeblo/integration-slack/credentials"; -import { slackProviderKey } from "@feeblo/integration-slack/manifest"; +import { + slackCommandsCapabilityKey, + slackMessageActionCapabilityKey, + slackProviderKey, +} from "@feeblo/integration-slack/manifest"; import { and, desc, eq, inArray } from "drizzle-orm"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; @@ -98,7 +102,7 @@ export const makeSlackConnectionServiceLive = ( const config = yield* SlackIntegrationConfig; const connectStart = Effect.fn("SlackConnection.connectStart")( - function* ({ organizationId }: { readonly organizationId: string }) { + function* ({ organizationId }: S.TSlackConnectStart) { if (!config.configured) { return yield* new InternalServerError({ message: "Slack integration is not configured", @@ -318,9 +322,9 @@ export const makeSlackConnectionServiceLive = ( // Duplicate replays are ignored via the connection/capability // unique index. for (const capabilityKey of [ - "commands", - "message.action", - ] as const) { + slackCommandsCapabilityKey, + slackMessageActionCapabilityKey, + ]) { yield* db .insert(schema.integrationRouteTable) .values({ diff --git a/packages/domain/src/integration/slack/slack-feedback-service.ts b/packages/domain/src/integration/slack/slack-feedback-service.ts index b0cc1188..ed965672 100644 --- a/packages/domain/src/integration/slack/slack-feedback-service.ts +++ b/packages/domain/src/integration/slack/slack-feedback-service.ts @@ -142,6 +142,7 @@ export const makeSlackFeedbackServiceLive = (): Layer.Layer< yield* recordPostIntegrationEvent({ actor: { kind: "end_user" }, boardId: asLegid(BoardId)(boardId), + description: sanitizedMarkdown, eventType: "feedback.post.created", organizationId: asLegid(WorkspaceId)(organizationId), postId: id, diff --git a/packages/domain/src/integration/webhook-management-live.ts b/packages/domain/src/integration/webhook-management-live.ts index 8f199b26..2e789843 100644 --- a/packages/domain/src/integration/webhook-management-live.ts +++ b/packages/domain/src/integration/webhook-management-live.ts @@ -21,6 +21,7 @@ import { generateWebhookSigningSecret, resolveAndParseWebhookEndpoint, rotateWebhookSigningKeyring, + webhookEventsPostCapabilityKey, webhookProviderKey, } from "@feeblo/integration-webhook"; import { and, desc, eq, inArray, lt, or, sql } from "drizzle-orm"; @@ -270,7 +271,7 @@ export const WebhookManagementServiceLive = Layer.effect( safeDisplayMetadata: { hostname: validated.hostname }, }); yield* db.insert(schema.integrationRouteTable).values({ - capabilityKey: "events.post", + capabilityKey: webhookEventsPostCapabilityKey, configVersion: 1, connectionId, enabled: true, diff --git a/packages/domain/src/notification/service.ts b/packages/domain/src/notification/service.ts index e940b72a..dc552359 100644 --- a/packages/domain/src/notification/service.ts +++ b/packages/domain/src/notification/service.ts @@ -195,6 +195,41 @@ const makeNotificationService = Effect.gen(function* () { }); }), + /** Notifies only members who upvoted a post; upvoting intentionally does not imply subscription. */ + notifyPostStatusChangedUpvoters: ({ + actorMemberId, + organizationId, + postId, + deduplicationKey, + }: PostNotificationInput & { readonly deduplicationKey: string }) => + Effect.gen(function* () { + const context = yield* getPostContext({ organizationId, postId }); + if (!context) { + return; + } + const upvoters = yield* db + .select({ memberId: schema.upvoteTable.memberId }) + .from(schema.upvoteTable) + .where( + and( + eq(schema.upvoteTable.organizationId, organizationId), + eq(schema.upvoteTable.postId, postId) + ) + ); + yield* create({ + ...(actorMemberId === undefined ? {} : { actorMemberId }), + organizationId, + recipientMemberIds: upvoters.map((upvoter) => upvoter.memberId), + kind: "feedback.status_changed", + resourceType: "post", + resourceId: postId, + title: "Feedback status updated", + body: context.title, + href: `/${organizationId}/post/${context.boardSlug}/${context.slug}`, + deduplicationKey, + }); + }), + list: ({ organizationId, recipientMemberId, diff --git a/packages/domain/src/og-image/errors.ts b/packages/domain/src/og-image/errors.ts index 2f850d09..11e3029a 100644 --- a/packages/domain/src/og-image/errors.ts +++ b/packages/domain/src/og-image/errors.ts @@ -1,18 +1,18 @@ import * as Schema from "effect/Schema"; -export class OgImageRequestValidationError extends Schema.TaggedErrorClass()( +export class OgImageRequestValidationError extends Schema.TaggedError()( "OgImageRequestValidationError", { message: Schema.String }, { httpApiStatus: 400, identifier: "OgImageRequestValidationError" } ) {} -export class OgImageSiteNotFoundError extends Schema.TaggedErrorClass()( +export class OgImageSiteNotFoundError extends Schema.TaggedError()( "OgImageSiteNotFoundError", { siteId: Schema.String }, { httpApiStatus: 404, identifier: "OgImageSiteNotFoundError" } ) {} -export class OgImagePostNotFoundError extends Schema.TaggedErrorClass()( +export class OgImagePostNotFoundError extends Schema.TaggedError()( "OgImagePostNotFoundError", { postSlug: Schema.String, @@ -21,7 +21,7 @@ export class OgImagePostNotFoundError extends Schema.TaggedErrorClass()( +export class OgImageRenderError extends Schema.TaggedError()( "OgImageRenderError", { cause: Schema.Any }, { httpApiStatus: 500, identifier: "OgImageRenderError" } diff --git a/packages/domain/src/policy.ts b/packages/domain/src/policy.ts index 5db7f3f3..da83fc58 100644 --- a/packages/domain/src/policy.ts +++ b/packages/domain/src/policy.ts @@ -26,7 +26,7 @@ export type PublicPolicy = Effect.Effect< R >; -export class PolicyDeniedError extends Schema.TaggedErrorClass()( +export class PolicyDeniedError extends Schema.TaggedError()( "PolicyDenied", { reason: Schema.optional(Schema.String) }, { httpApiStatus: 403 } diff --git a/packages/domain/src/post/errors.ts b/packages/domain/src/post/errors.ts index 6809d657..c7939e66 100644 --- a/packages/domain/src/post/errors.ts +++ b/packages/domain/src/post/errors.ts @@ -7,31 +7,31 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class FailedToCreatePostError extends Schema.TaggedErrorClass()( +export class FailedToCreatePostError extends Schema.TaggedError()( "FailedToCreatePostError", {}, { httpApiStatus: 500, identifier: "FailedToCreatePostError" } ) {} -export class PostAlreadyExistsError extends Schema.TaggedErrorClass()( +export class PostAlreadyExistsError extends Schema.TaggedError()( "PostAlreadyExistsError", { message: Schema.optional(Schema.String) }, { httpApiStatus: 409, identifier: "PostAlreadyExistsError" } ) {} -export class FailedToDeletePostError extends Schema.TaggedErrorClass()( +export class FailedToDeletePostError extends Schema.TaggedError()( "FailedToDeletePostError", {}, { httpApiStatus: 500, identifier: "FailedToDeletePostError" } ) {} -export class FailedToUpdatePostError extends Schema.TaggedErrorClass()( +export class FailedToUpdatePostError extends Schema.TaggedError()( "FailedToUpdatePostError", {}, { httpApiStatus: 500, identifier: "FailedToUpdatePostError" } ) {} -export class FailedToMergePostError extends Schema.TaggedErrorClass()( +export class FailedToMergePostError extends Schema.TaggedError()( "FailedToMergePostError", { message: Schema.String }, { httpApiStatus: 500, identifier: "FailedToMergePostError" } diff --git a/packages/domain/src/post/handlers.ts b/packages/domain/src/post/handlers.ts index 64747866..b9a823aa 100644 --- a/packages/domain/src/post/handlers.ts +++ b/packages/domain/src/post/handlers.ts @@ -87,6 +87,7 @@ export const PostRpcHandlersEffect = Effect.gen(function* () { actorMemberId, actorName, boardId, + description, eventType, organizationId, postId, @@ -98,6 +99,7 @@ export const PostRpcHandlersEffect = Effect.gen(function* () { actorMemberId: string | null; actorName: string | null | undefined; boardId: LegidOf<"BoardId">; + description?: string; eventType: "feedback.post.created" | "feedback.post.status_changed"; organizationId: LegidOf<"WorkspaceId">; postId: LegidOf<"PostId">; @@ -118,6 +120,7 @@ export const PostRpcHandlersEffect = Effect.gen(function* () { memberId: actorMemberId, }, boardId, + ...(description === undefined ? {} : { description }), eventType, organizationId, postId, @@ -638,6 +641,7 @@ export const PostRpcHandlersEffect = Effect.gen(function* () { actorMemberId: membership?.membershipId ?? null, actorName: membership ? session.user.name : undefined, boardId: args.boardId, + description: prepared.content, eventType: "feedback.post.created", organizationId: args.organizationId, postId: args.id, diff --git a/packages/domain/src/post/workflow.ts b/packages/domain/src/post/workflow.ts index 616bf3b4..18a84f83 100644 --- a/packages/domain/src/post/workflow.ts +++ b/packages/domain/src/post/workflow.ts @@ -11,7 +11,7 @@ import * as Effect from "effect/Effect"; import * as S from "effect/Schema"; import * as W from "effect/unstable/workflow"; -class SubmissionNotificationDataError extends S.TaggedErrorClass()( +class SubmissionNotificationDataError extends S.TaggedError()( "SubmissionNotificationDataError", { operation: S.String, diff --git a/packages/domain/src/rate-limit.ts b/packages/domain/src/rate-limit.ts index 074471c7..867dd560 100644 --- a/packages/domain/src/rate-limit.ts +++ b/packages/domain/src/rate-limit.ts @@ -32,13 +32,13 @@ export interface PublicRpcRateLimitOptions { readonly window?: Duration.Input; } -export class RateLimitExceededError extends Schema.TaggedErrorClass()( +export class RateLimitExceededError extends Schema.TaggedError()( "RateLimitExceededError", {}, { httpApiStatus: 429, identifier: "RateLimitExceededError" } ) {} -export class RateLimitUnavailableError extends Schema.TaggedErrorClass()( +export class RateLimitUnavailableError extends Schema.TaggedError()( "RateLimitUnavailableError", {}, { httpApiStatus: 503, identifier: "RateLimitUnavailableError" } diff --git a/packages/domain/src/rpc-errors.ts b/packages/domain/src/rpc-errors.ts index d966b241..4db53d5b 100644 --- a/packages/domain/src/rpc-errors.ts +++ b/packages/domain/src/rpc-errors.ts @@ -94,7 +94,7 @@ type RemappedDbEffect = Effect.Effect< A >; -export class BadRequestError extends Schema.TaggedErrorClass()( +export class BadRequestError extends Schema.TaggedError()( "BadRequestError", { message: Schema.optional(Schema.String), @@ -102,7 +102,7 @@ export class BadRequestError extends Schema.TaggedErrorClass()( { httpApiStatus: 400, identifier: "BadRequestError" } ) {} -export class NotFoundError extends Schema.TaggedErrorClass()( +export class NotFoundError extends Schema.TaggedError()( "NotFoundError", { message: Schema.optional(Schema.String), @@ -110,7 +110,7 @@ export class NotFoundError extends Schema.TaggedErrorClass()( { httpApiStatus: 404, identifier: "NotFoundError" } ) {} -export class UnauthorizedError extends Schema.TaggedErrorClass()( +export class UnauthorizedError extends Schema.TaggedError()( "UnauthorizedError", { message: Schema.optional(Schema.String), @@ -118,7 +118,7 @@ export class UnauthorizedError extends Schema.TaggedErrorClass()( +export class InternalServerError extends Schema.TaggedError()( "InternalServerError", { message: Schema.String, diff --git a/packages/domain/src/rpc-group.ts b/packages/domain/src/rpc-group.ts index b5e4cd75..37808106 100644 --- a/packages/domain/src/rpc-group.ts +++ b/packages/domain/src/rpc-group.ts @@ -10,6 +10,8 @@ import { CompanyRpcs } from "./company/rpcs"; import { ContactRpcs } from "./contact/rpcs"; import { EmailSubscriptionRpcs } from "./email-subscription/rpcs"; import { DiscordManagementRpcs } from "./integration/discord/rpcs"; +import { ExternalResourceRpcs } from "./integration/external-resource/rpcs"; +import { GitHubManagementRpcs } from "./integration/github/rpcs"; import { WebhookManagementRpcs } from "./integration/rpcs"; import { SlackManagementRpcs } from "./integration/slack/rpcs"; import { JwtSecretRpcs } from "./jwt-secret/rpcs"; @@ -53,5 +55,7 @@ export const AllRpcs = PostRpcs.merge(PostActivityRpcs).merge( EmailSubscriptionRpcs, WebhookManagementRpcs, SlackManagementRpcs, - DiscordManagementRpcs + DiscordManagementRpcs, + ExternalResourceRpcs, + GitHubManagementRpcs ); diff --git a/packages/domain/src/rpc-router.ts b/packages/domain/src/rpc-router.ts index df7aa6f7..57d51c0d 100644 --- a/packages/domain/src/rpc-router.ts +++ b/packages/domain/src/rpc-router.ts @@ -15,6 +15,8 @@ import { CompanyRpcHandlers } from "./company/handlers"; import { ContactRpcHandlers } from "./contact/handlers"; import { EmailSubscriptionRpcHandlers } from "./email-subscription/handlers"; import { DiscordManagementRpcHandlers } from "./integration/discord/handlers"; +import { ExternalResourceRpcHandlers } from "./integration/external-resource/handlers"; +import { GitHubManagementRpcHandlers } from "./integration/github/handlers"; import { WebhookManagementRpcHandlers } from "./integration/handlers"; import { SlackManagementRpcHandlers } from "./integration/slack/handlers"; import { JwtSecretRpcHandlers } from "./jwt-secret/handlers"; @@ -51,7 +53,9 @@ export const RpcRoute = RpcServer.layerHttp({ PostActivityRpcHandlers, WebhookManagementRpcHandlers, SlackManagementRpcHandlers, - DiscordManagementRpcHandlers + DiscordManagementRpcHandlers, + ExternalResourceRpcHandlers, + GitHubManagementRpcHandlers ) ), Layer.provide(BillingRpcHandlers), diff --git a/packages/domain/src/site/subdomain/errors.ts b/packages/domain/src/site/subdomain/errors.ts index 69246e1c..9675a276 100644 --- a/packages/domain/src/site/subdomain/errors.ts +++ b/packages/domain/src/site/subdomain/errors.ts @@ -1,13 +1,13 @@ import * as Schema from "effect/Schema"; -export class ProfanityError extends Schema.TaggedErrorClass()( +export class ProfanityError extends Schema.TaggedError()( "ProfanityError", { message: Schema.String }, { httpApiStatus: 400, identifier: "ProfanityError" } ) {} -export class ReservedSubdomainError extends Schema.TaggedErrorClass()( +export class ReservedSubdomainError extends Schema.TaggedError()( "ReservedSubdomainError", { message: Schema.String }, { httpApiStatus: 400, identifier: "ReservedSubdomainError" } diff --git a/packages/domain/src/tag/errors.ts b/packages/domain/src/tag/errors.ts index 3dd1465c..64ab6f67 100644 --- a/packages/domain/src/tag/errors.ts +++ b/packages/domain/src/tag/errors.ts @@ -7,25 +7,25 @@ import { UnauthorizedError, } from "../rpc-errors"; -export class FailedToCreateTagError extends Schema.TaggedErrorClass()( +export class FailedToCreateTagError extends Schema.TaggedError()( "FailedToCreateTagError", {}, { httpApiStatus: 500, identifier: "FailedToCreateTagError" } ) {} -export class FailedToUpdateTagError extends Schema.TaggedErrorClass()( +export class FailedToUpdateTagError extends Schema.TaggedError()( "FailedToUpdateTagError", {}, { httpApiStatus: 500, identifier: "FailedToUpdateTagError" } ) {} -export class FailedToDeleteTagError extends Schema.TaggedErrorClass()( +export class FailedToDeleteTagError extends Schema.TaggedError()( "FailedToDeleteTagError", {}, { httpApiStatus: 500, identifier: "FailedToDeleteTagError" } ) {} -export class FailedToSetTagAssignmentsError extends Schema.TaggedErrorClass()( +export class FailedToSetTagAssignmentsError extends Schema.TaggedError()( "FailedToSetTagAssignmentsError", {}, { httpApiStatus: 500, identifier: "FailedToSetTagAssignmentsError" } diff --git a/packages/domain/src/user/errors.ts b/packages/domain/src/user/errors.ts index 4448119d..94c23941 100644 --- a/packages/domain/src/user/errors.ts +++ b/packages/domain/src/user/errors.ts @@ -1,6 +1,6 @@ import * as Schema from "effect/Schema"; -export class UserPersistenceError extends Schema.TaggedErrorClass()( +export class UserPersistenceError extends Schema.TaggedError()( "UserPersistenceError", { message: Schema.String }, { httpApiStatus: 500, identifier: "UserPersistenceError" } diff --git a/packages/domain/src/widget/api-live.ts b/packages/domain/src/widget/api-live.ts index db0e62f3..a327842d 100644 --- a/packages/domain/src/widget/api-live.ts +++ b/packages/domain/src/widget/api-live.ts @@ -204,6 +204,7 @@ export const WidgetApiLive = HttpApiBuilder.group( recordPostIntegrationEvent({ actor: { kind: "end_user" }, boardId, + description: sanitizedContent, eventType: "feedback.post.created", organizationId, postId: id, diff --git a/packages/domain/src/widget/sso.ts b/packages/domain/src/widget/sso.ts index 9ac05a69..18b685b9 100644 --- a/packages/domain/src/widget/sso.ts +++ b/packages/domain/src/widget/sso.ts @@ -32,7 +32,7 @@ import { UserRepository } from "../user/repository"; * better-auth `APIError` by the jwt-auto-login plugin, keeping all error-code * knowledge inside the plugin. */ -export class SsoError extends S.TaggedErrorClass()("SsoError", { +export class SsoError extends S.TaggedError()("SsoError", { code: S.Literals([ "ORGANIZATION_HAS_NO_JWT_SECRET", "INVALID_JWT", diff --git a/packages/domain/src/workspace/errors.ts b/packages/domain/src/workspace/errors.ts index 5694f9a3..739abe83 100644 --- a/packages/domain/src/workspace/errors.ts +++ b/packages/domain/src/workspace/errors.ts @@ -11,7 +11,7 @@ import { ReservedSubdomainError, } from "../site/subdomain/errors"; -export class FailedToCreateWorkspaceError extends Schema.TaggedErrorClass()( +export class FailedToCreateWorkspaceError extends Schema.TaggedError()( "FailedToCreateWorkspaceError", { message: Schema.optional(Schema.String), diff --git a/packages/id/src/index.ts b/packages/id/src/index.ts index a4c99da2..642b15ee 100644 --- a/packages/id/src/index.ts +++ b/packages/id/src/index.ts @@ -181,3 +181,40 @@ export const IntegrationDeliveryAttemptId = makeId( "ida", { approximateLength } ); + +/** Identifies one provider-owned external resource. */ +export const IntegrationExternalResourceId = makeId( + "integration_external_resource", + "ier", + { + approximateLength, + } +); + +/** Identifies one many-to-many relationship between a Feeblo post and an external resource. */ +export const PostExternalResourceLinkId = makeId( + "post_external_resource_link", + "erl", + { + approximateLength, + } +); + +/** Identifies one organization-owned GitHub issue status rule. */ +export const GitHubSyncRuleId = makeId("github_sync_rule", "gsr", { + approximateLength, +}); + +/** Identifies one deduplicated GitHub webhook delivery. */ +export const GitHubWebhookDeliveryId = makeId( + "github_webhook_delivery", + "gwd", + { approximateLength } +); + +/** Identifies a durable manually requested external-resource creation. */ +export const ExternalResourceCreateRequestId = makeId( + "external_resource_create_request", + "erc", + { approximateLength } +); diff --git a/packages/id/src/legid.ts b/packages/id/src/legid.ts index 6e6317d5..7660058e 100644 --- a/packages/id/src/legid.ts +++ b/packages/id/src/legid.ts @@ -14,13 +14,10 @@ export type LegidId = typeof LegidId.Type; export type LegidOf = LegidId & Brand.Brand; -export class LegidError extends Schema.TaggedErrorClass()( - "LegidError", - { - input: Schema.String, - message: Schema.String, - } -) {} +export class LegidError extends Schema.TaggedError()("LegidError", { + input: Schema.String, + message: Schema.String, +}) {} export interface LegidConfig { readonly approximateLength?: number; diff --git a/packages/transactional/src/mailer.ts b/packages/transactional/src/mailer.ts index 6186222e..27e04d2f 100644 --- a/packages/transactional/src/mailer.ts +++ b/packages/transactional/src/mailer.ts @@ -102,7 +102,7 @@ export interface MailerService { } /** Rendering failed before a provider request was made. */ -export class MailTemplateRenderError extends Schema.TaggedErrorClass()( +export class MailTemplateRenderError extends Schema.TaggedError()( "MailTemplateRenderError", { cause: Schema.optionalKey(Schema.Defect()), @@ -112,7 +112,7 @@ export class MailTemplateRenderError extends Schema.TaggedErrorClass()( +export class MailPermanentDeliveryError extends Schema.TaggedError()( "MailPermanentDeliveryError", { cause: Schema.optionalKey(Schema.Defect()), @@ -125,7 +125,7 @@ export class MailPermanentDeliveryError extends Schema.TaggedErrorClass()( +export class MailTemporaryDeliveryError extends Schema.TaggedError()( "MailTemporaryDeliveryError", { cause: Schema.optionalKey(Schema.Defect()), @@ -138,7 +138,7 @@ export class MailTemporaryDeliveryError extends Schema.TaggedErrorClass()( +export class MailUncertainDeliveryError extends Schema.TaggedError()( "MailUncertainDeliveryError", { cause: Schema.optionalKey(Schema.Defect()), diff --git a/packages/web-shared/package.json b/packages/web-shared/package.json index cacc2271..4d9c6862 100644 --- a/packages/web-shared/package.json +++ b/packages/web-shared/package.json @@ -100,7 +100,7 @@ "test": "vitest run" }, "dependencies": { - "@effect/atom-react": "4.0.0-beta.66", + "@effect/atom-react": "catalog:", "@epic-web/client-hints": "^1.3.9", "@feeblo/auth": "workspace:*", "@feeblo/db": "workspace:*", diff --git a/packages/web-shared/src/auth/atoms.ts b/packages/web-shared/src/auth/atoms.ts index 096f21e3..57b05678 100644 --- a/packages/web-shared/src/auth/atoms.ts +++ b/packages/web-shared/src/auth/atoms.ts @@ -19,7 +19,7 @@ import { authClient } from "../lib/auth-client"; // distinguish them from an authoritative signed-out response. // --------------------------------------------------------------------------- -export class AuthSessionRequestError extends Schema.TaggedErrorClass()( +export class AuthSessionRequestError extends Schema.TaggedError()( "AuthSessionRequestError", { cause: Schema.Defect() } ) {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61f9227e..69f683c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,20 +19,29 @@ catalogs: specifier: ^1.75.1 version: 1.75.1 '@effect-aws/client-s3': - specifier: ^2.0.0-beta.4 + specifier: 2.0.0-beta.6 version: 2.0.0-beta.6 '@effect-aws/s3': - specifier: ^1.0.0-beta.4 + specifier: 1.0.0-beta.5 version: 1.0.0-beta.5 + '@effect/ai-openai': + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 + '@effect/atom-react': + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 '@effect/platform-node': - specifier: ^4.0.0-beta.66 - version: 4.0.0-beta.103 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 '@effect/sql-pg': - specifier: ^4.0.0-beta.66 - version: 4.0.0-beta.103 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 '@effect/sql-pglite': - specifier: ^4.0.0-beta.66 - version: 4.0.0-beta.103 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 + '@effect/vitest': + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 '@floating-ui/dom': specifier: ^1.8.0 version: 1.8.0 @@ -50,16 +59,16 @@ catalogs: version: 4.3.3 '@tanstack/query-db-collection': specifier: ^1.2.1 - version: 1.2.1 + version: 1.2.2 '@tanstack/react-db': specifier: ^0.1.95 - version: 0.1.95 + version: 0.1.96 '@tanstack/react-form': specifier: ^1.33.3 - version: 1.33.3 + version: 1.33.5 '@tanstack/react-router': specifier: ^1.170.19 - version: 1.170.19 + version: 1.170.27 '@tanstack/react-virtual': specifier: ^3.13.23 version: 3.14.9 @@ -91,14 +100,14 @@ catalogs: specifier: 1.0.0-rc.4 version: 1.0.0-rc.4 drizzle-orm: - specifier: 1.0.0-rc.4 - version: 1.0.0-rc.4 + specifier: 1.0.0-rc.5-169397b + version: 1.0.0-rc.5-169397b drizzle-seed: specifier: 1.0.0-rc.4 version: 1.0.0-rc.4 effect: - specifier: ^4.0.0-beta.66 - version: 4.0.0-beta.103 + specifier: 4.0.0-beta.107 + version: 4.0.0-beta.107 jose: specifier: ^6.2.3 version: 6.2.8 @@ -128,7 +137,7 @@ catalogs: version: 4.3.3 tsx: specifier: ^4.20.6 - version: 4.23.6 + version: 4.23.12 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -137,7 +146,7 @@ catalogs: version: 6.0.3 vite: specifier: ^8.0.16 - version: 8.2.0 + version: 8.2.1 vite-plugin-icons-spritesheet: specifier: ^3.1.0 version: 3.1.0 @@ -153,6 +162,7 @@ catalogs: overrides: prosemirror-model: ^1.25.9 + '@effect/platform-node-shared': 4.0.0-beta.107 importers: @@ -181,7 +191,7 @@ importers: version: 17.4.2 turbo: specifier: ^2.10.8 - version: 2.10.8 + version: 2.10.9 typescript: specifier: 'catalog:' version: 6.0.3 @@ -220,22 +230,22 @@ importers: version: 1.1.9(react@19.2.8) '@tanstack/query-db-collection': specifier: 'catalog:' - version: 1.2.1(@tanstack/query-core@5.101.4)(typescript@6.0.3) + version: 1.2.2(@tanstack/query-core@5.101.4)(typescript@6.0.3) '@tanstack/react-db': specifier: 'catalog:' - version: 0.1.95(react@19.2.8)(typescript@6.0.3) + version: 0.1.96(react@19.2.8)(typescript@6.0.3) '@tanstack/react-query': specifier: ^5.101.0 version: 5.101.4(react@19.2.8) '@tanstack/react-router': specifier: 'catalog:' - version: 1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8) clsx: specifier: 'catalog:' version: 2.1.1 effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 react: specifier: 'catalog:' version: 19.2.8 @@ -260,13 +270,13 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) apps/server: dependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.103(effect@4.0.0-beta.103)(ioredis@5.11.1) + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.11.1) '@feeblo/auth': specifier: workspace:* version: link:../../packages/auth @@ -288,6 +298,9 @@ importers: '@feeblo/integration-discord': specifier: workspace:* version: link:../../integrations/discord + '@feeblo/integration-github': + specifier: workspace:* + version: link:../../integrations/github '@feeblo/integration-slack': specifier: workspace:* version: link:../../integrations/slack @@ -302,14 +315,17 @@ importers: version: link:../../packages/utils '@sentry/effect': specifier: ^10.67.0 - version: 10.69.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(effect@4.0.0-beta.103) + version: 10.70.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(effect@4.0.0-beta.107) drizzle-orm: specifier: 'catalog:' - version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + version: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 devDependencies: + '@effect/vitest': + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../../packages/config @@ -318,25 +334,28 @@ importers: version: 1.0.0-rc.16 tsx: specifier: 'catalog:' - version: 4.23.6 + version: 4.23.12 typescript: specifier: 'catalog:' version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) apps/web: dependencies: '@astrojs/node': specifier: 11.0.0 - version: 11.0.0(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0)) + version: 11.0.0(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@astrojs/react': specifier: ^6.0.2 - version: 6.0.2(@types/node@26.1.2)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(jiti@2.7.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.6)(yaml@2.9.0) + version: 6.0.2(@types/node@26.2.0)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.2)(jiti@2.7.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.12)(yaml@2.9.0) '@astrojs/rss': specifier: ^4.0.19 version: 4.0.19 '@astrojs/solid-js': specifier: ^7.0.1 - version: 7.0.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(solid-js@1.9.14)(tsx@4.23.6)(yaml@2.9.0) + version: 7.0.2(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(solid-js@1.9.14)(tsx@4.23.12)(yaml@2.9.0) '@base-ui/react': specifier: 'catalog:' version: 1.7.0(@date-fns/tz@1.5.0)(@types/react@19.2.18)(date-fns@4.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -350,8 +369,8 @@ importers: specifier: ^0.3.2 version: 0.3.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@effect/atom-react': - specifier: 4.0.0-beta.66 - version: 4.0.0-beta.66(effect@4.0.0-beta.103)(react@19.2.8)(scheduler@0.27.0) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(react@19.2.8)(scheduler@0.27.0) '@egoist/tailwindcss-icons': specifier: ^1.9.2 version: 1.9.2(tailwindcss@4.3.3) @@ -402,34 +421,34 @@ importers: version: 1.1.9(react@19.2.8) '@iconify-json/lucide': specifier: ^1.2.121 - version: 1.2.121 + version: 1.2.123 '@marsidev/react-turnstile': specifier: ^1.5.4 - version: 1.5.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.6.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@posthog/react': specifier: ^1.10.3 - version: 1.10.3(@types/react@19.2.18)(posthog-js@1.411.0)(react@19.2.8) + version: 1.10.3(@types/react@19.2.18)(posthog-js@1.416.0)(react@19.2.8) '@tanstack/query-db-collection': specifier: 'catalog:' - version: 1.2.1(@tanstack/query-core@5.101.4)(typescript@6.0.3) + version: 1.2.4(@tanstack/query-core@5.101.4)(typescript@6.0.3) '@tanstack/react-db': specifier: 'catalog:' - version: 0.1.95(react@19.2.8)(typescript@6.0.3) + version: 0.1.96(react@19.2.8)(typescript@6.0.3) '@tanstack/react-form': specifier: 'catalog:' - version: 1.33.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.33.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-query': specifier: ^5.101.4 version: 5.101.4(react@19.2.8) '@tanstack/react-router': specifier: 'catalog:' - version: 1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-router-devtools': specifier: ^1.167.1 - version: 1.167.1(@tanstack/react-router@1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@tanstack/router-core@1.171.16)(csstype@3.2.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.167.1(@tanstack/react-router@1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@tanstack/router-core@1.171.22)(csstype@3.2.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-store': specifier: ^0.11.0 - version: 0.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-virtual': specifier: 'catalog:' version: 3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -447,13 +466,13 @@ importers: version: 1.0.1(react@19.2.8)(solid-js@1.9.14) astro: specifier: ^7.1.6 - version: 7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0) + version: 7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) astro-seo: specifier: ^1.1.0 version: 1.1.0(prettier@3.9.6)(typescript@6.0.3) better-auth: specifier: 'catalog:' - version: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) + version: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -465,10 +484,10 @@ importers: version: 17.4.2 effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 posthog-js: specifier: ^1.411.0 - version: 1.411.0 + version: 1.416.0 react: specifier: 'catalog:' version: 19.2.8 @@ -480,7 +499,7 @@ importers: version: 19.2.8(react@19.2.8) shadcn: specifier: 'catalog:' - version: 3.8.5(@types/node@26.1.2)(typescript@6.0.3) + version: 3.8.5(@types/node@26.2.0)(typescript@6.0.3) solid-js: specifier: 'catalog:' version: 1.9.14 @@ -492,14 +511,14 @@ importers: version: 1.4.0 wrangler: specifier: ^4.118.0 - version: 4.118.0(@cloudflare/workers-types@4.20260702.1) + version: 4.122.0(@cloudflare/workers-types@4.20260702.1) zod: specifier: 'catalog:' version: 4.4.3 devDependencies: '@astrojs/cloudflare': specifier: ^14.1.7 - version: 14.1.7(@types/node@26.1.2)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0) + version: 14.2.1(@types/node@26.2.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0) '@cloudflare/workers-types': specifier: 'catalog:' version: 4.20260702.1 @@ -511,28 +530,28 @@ importers: version: 3.3.0 '@inlang/paraglide-js': specifier: ^2.23.1 - version: 2.23.1(typescript@6.0.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 2.23.2(typescript@6.0.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@tailwindcss/typography': specifier: ^0.5.20 version: 0.5.20(tailwindcss@4.3.3) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@tanstack/react-devtools': specifier: ^0.9.6 version: 0.9.13(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(csstype@3.2.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14) '@tanstack/react-form-devtools': specifier: ^0.2.32 - version: 0.2.32(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14) + version: 0.2.34(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14) '@tanstack/react-query-devtools': specifier: ^5.101.4 version: 5.101.4(@tanstack/react-query@5.101.4(react@19.2.8))(react@19.2.8) '@tanstack/router-plugin': specifier: ^1.168.24 - version: 1.168.24(@tanstack/react-router@1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(esbuild@0.28.1)(rolldown@1.2.2)(rollup@4.62.4)(vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 1.168.30(@tanstack/react-router@1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) alchemy: specifier: 'catalog:' - version: 0.82.2(@astrojs/cloudflare@14.1.7(@types/node@26.1.2)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0))(@aws-sdk/client-s3@3.1103.0)(@cloudflare/vite-plugin@1.50.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1)))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(workerd@1.20260730.1)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1)) + version: 0.82.2(@astrojs/cloudflare@14.2.1(@types/node@26.2.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0))(@aws-sdk/client-s3@3.1109.0)(@cloudflare/vite-plugin@1.52.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1)))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(workerd@1.20260811.1)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1)) tailwindcss: specifier: 'catalog:' version: 4.3.3 @@ -541,7 +560,7 @@ importers: version: 6.0.3 vite-plugin-icons-spritesheet: specifier: 'catalog:' - version: 3.1.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 3.1.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) e2e: devDependencies: @@ -559,7 +578,7 @@ importers: version: 24.13.3 tsx: specifier: 'catalog:' - version: 4.23.6 + version: 4.23.12 integrations/core: dependencies: @@ -574,14 +593,14 @@ importers: version: link:../../packages/utils drizzle-orm: specifier: 'catalog:' - version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + version: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 devDependencies: '@effect/vitest': - specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../../packages/config @@ -590,7 +609,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) integrations/discord: dependencies: @@ -602,11 +621,45 @@ importers: version: link:../../packages/utils effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 devDependencies: '@effect/vitest': - specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) + '@feeblo/config': + specifier: workspace:* + version: link:../../packages/config + '@feeblo/id': + specifier: workspace:* + version: link:../../packages/id + '@types/node': + specifier: 'catalog:' + version: 24.13.3 + typescript: + specifier: 'catalog:' + version: 6.0.3 + vitest: + specifier: 'catalog:' + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + + integrations/github: + dependencies: + '@distilled.cloud/github': + specifier: 1.0.0-rc.4 + version: 1.0.0-rc.4(effect@4.0.0-beta.107) + '@feeblo/integration-core': + specifier: workspace:* + version: link:../core + effect: + specifier: 'catalog:' + version: 4.0.0-beta.107 + jose: + specifier: 'catalog:' + version: 6.2.8 + devDependencies: + '@effect/vitest': + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../../packages/config @@ -621,7 +674,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) integrations/slack: dependencies: @@ -633,11 +686,11 @@ importers: version: link:../../packages/utils effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 devDependencies: '@effect/vitest': - specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../../packages/config @@ -652,13 +705,13 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) integrations/webhook: dependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.103(effect@4.0.0-beta.103)(ioredis@5.11.1) + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.11.1) '@feeblo/integration-core': specifier: workspace:* version: link:../core @@ -667,14 +720,14 @@ importers: version: link:../../packages/utils effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 standardwebhooks: specifier: 1.0.0 version: 1.0.0 devDependencies: '@effect/vitest': - specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../../packages/config @@ -689,13 +742,13 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/auth: dependencies: '@effect/platform-node': specifier: 'catalog:' - version: 4.0.0-beta.103(effect@4.0.0-beta.103)(ioredis@5.11.1) + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.11.1) '@feeblo/config': specifier: workspace:* version: link:../config @@ -719,22 +772,22 @@ importers: version: link:../utils '@polar-sh/better-auth': specifier: 'catalog:' - version: 1.8.4(@polar-sh/sdk@0.47.1)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@stripe/stripe-js@7.9.0)(better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10))(react@19.2.8)(zod@4.4.3) + version: 1.8.4(@polar-sh/sdk@0.47.1)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@stripe/stripe-js@7.9.0)(better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10))(react@19.2.8)(zod@4.4.3) '@polar-sh/sdk': specifier: 'catalog:' version: 0.47.1 better-auth: specifier: 'catalog:' - version: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) + version: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) drizzle-orm: specifier: 'catalog:' - version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + version: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) zod: specifier: 'catalog:' version: 4.4.3 @@ -747,7 +800,7 @@ importers: dependencies: effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 devDependencies: typescript: specifier: 'catalog:' @@ -757,10 +810,10 @@ importers: dependencies: '@effect/sql-pg': specifier: 'catalog:' - version: 4.0.0-beta.103(effect@4.0.0-beta.103) + version: 4.0.0-beta.107(effect@4.0.0-beta.107) '@effect/sql-pglite': specifier: 'catalog:' - version: 4.0.0-beta.103(effect@4.0.0-beta.103) + version: 4.0.0-beta.107(effect@4.0.0-beta.107) '@electric-sql/pglite': specifier: ^0.5.4 version: 0.5.4 @@ -775,10 +828,10 @@ importers: version: 17.4.2 drizzle-orm: specifier: 'catalog:' - version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + version: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 postgres: specifier: 'catalog:' version: 3.4.9 @@ -788,7 +841,7 @@ importers: devDependencies: '@effect/vitest': specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + version: 4.0.0-beta.94(effect@4.0.0-beta.107)(vitest@4.1.10) '@faker-js/faker': specifier: ^10.3.0 version: 10.5.0 @@ -803,22 +856,22 @@ importers: version: link:../utils '@types/pg': specifier: ^8.16.0 - version: 8.20.4 + version: 8.21.0 drizzle-kit: specifier: 'catalog:' version: 1.0.0-rc.4 drizzle-seed: specifier: 'catalog:' - version: 1.0.0-rc.4(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3)) + version: 1.0.0-rc.4(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3)) tsx: specifier: 'catalog:' - version: 4.23.6 + version: 4.23.12 typescript: specifier: 'catalog:' version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/db-migrator: dependencies: @@ -830,7 +883,7 @@ importers: version: 0.0.5(@electric-sql/pglite@0.5.4) drizzle-orm: specifier: 1.0.0-rc.4 - version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) postgres: specifier: ^3.4.7 version: 3.4.9 @@ -843,7 +896,7 @@ importers: version: 1.0.0-rc.16 tsx: specifier: 'catalog:' - version: 4.23.6 + version: 4.23.12 typescript: specifier: 'catalog:' version: 6.0.3 @@ -852,13 +905,13 @@ importers: dependencies: '@effect-aws/client-s3': specifier: 'catalog:' - version: 2.0.0-beta.6(effect@4.0.0-beta.103) + version: 2.0.0-beta.6(effect@4.0.0-beta.107) '@effect-aws/s3': specifier: 'catalog:' - version: 1.0.0-beta.5(@effect-aws/client-s3@2.0.0-beta.6(effect@4.0.0-beta.103))(effect@4.0.0-beta.103) + version: 1.0.0-beta.5(@effect-aws/client-s3@2.0.0-beta.6(effect@4.0.0-beta.107))(effect@4.0.0-beta.107) '@effect/ai-openai': - specifier: 4.0.0-beta.66 - version: 4.0.0-beta.66(effect@4.0.0-beta.103) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107) '@feeblo/db': specifier: workspace:* version: link:../db @@ -871,6 +924,9 @@ importers: '@feeblo/integration-discord': specifier: workspace:* version: link:../../integrations/discord + '@feeblo/integration-github': + specifier: workspace:* + version: link:../../integrations/github '@feeblo/integration-slack': specifier: workspace:* version: link:../../integrations/slack @@ -891,16 +947,16 @@ importers: version: 0.47.1 better-auth: specifier: 'catalog:' - version: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) + version: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) cookie-es: specifier: ^3.1.1 version: 3.1.1 drizzle-orm: specifier: 'catalog:' - version: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + version: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 ipaddr.js: specifier: ^2.5.0 version: 2.5.0 @@ -915,14 +971,11 @@ importers: version: 19.2.8 takumi-js: specifier: ^2.4.0 - version: 2.5.7(csstype@3.2.3)(preact@10.29.8)(react@19.2.8) + version: 2.7.2(csstype@3.2.3)(preact@10.29.8)(react@19.2.8) devDependencies: - '@effect/platform-node': - specifier: 'catalog:' - version: 4.0.0-beta.103(effect@4.0.0-beta.103)(ioredis@5.11.1) '@effect/vitest': - specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../config @@ -934,7 +987,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/feedback-widget: dependencies: @@ -968,10 +1021,10 @@ importers: version: link:../config '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) shadcn: specifier: 'catalog:' - version: 3.8.5(@types/node@26.1.2)(typescript@6.0.3) + version: 3.8.5(@types/node@26.2.0)(typescript@6.0.3) tailwindcss: specifier: 'catalog:' version: 4.3.3 @@ -983,26 +1036,26 @@ importers: version: 6.0.3 vite: specifier: 'catalog:' - version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) vite-plugin-icons-spritesheet: specifier: 'catalog:' - version: 3.1.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 3.1.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) vite-plugin-solid: specifier: 'catalog:' - version: 2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 2.11.14(solid-js@1.9.14)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/id: dependencies: effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 legid: specifier: ^0.1.4 version: 0.1.4 devDependencies: '@effect/vitest': specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + version: 4.0.0-beta.94(effect@4.0.0-beta.107)(vitest@4.1.10) '@feeblo/config': specifier: workspace:* version: link:../config @@ -1011,7 +1064,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/permissions: devDependencies: @@ -1023,7 +1076,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/post-ui: dependencies: @@ -1056,10 +1109,10 @@ importers: version: 0.6.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-db': specifier: 'catalog:' - version: 0.1.95(react@19.2.8)(typescript@6.0.3) + version: 0.1.96(react@19.2.8)(typescript@6.0.3) '@tanstack/react-form': specifier: 'catalog:' - version: 1.33.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.33.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: specifier: 'catalog:' version: 19.2.8 @@ -1078,10 +1131,10 @@ importers: version: link:../config '@ladle/react': specifier: ^5.1.1 - version: 5.1.1(@types/node@26.1.2)(@types/react@19.2.18)(jiti@2.7.0)(lightningcss@1.33.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.6)(typescript@6.0.3)(yaml@2.9.0) + version: 5.1.1(@types/node@26.2.0)(@types/react@19.2.18)(jiti@2.7.0)(lightningcss@1.33.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.12)(typescript@6.0.3)(yaml@2.9.0) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@types/react': specifier: 'catalog:' version: 19.2.18 @@ -1090,7 +1143,7 @@ importers: version: 19.2.4(@types/react@19.2.18) '@vitest/browser-playwright': specifier: ^4.1.10 - version: 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10) + version: 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) tailwindcss: specifier: 'catalog:' version: 4.3.3 @@ -1102,7 +1155,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) vitest-browser-react: specifier: ^2.2.0 version: 2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) @@ -1114,7 +1167,7 @@ importers: version: link:../domain effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 devDependencies: '@feeblo/config': specifier: workspace:* @@ -1133,16 +1186,16 @@ importers: version: 1.8.0 happy-dom: specifier: ^20.10.6 - version: 20.11.1 + version: 20.11.2 typescript: specifier: 'catalog:' version: 6.0.3 vite: specifier: 'catalog:' - version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/transactional: dependencies: @@ -1151,7 +1204,7 @@ importers: version: link:../config effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 nodemailer: specifier: ^8.0.2 version: 8.0.11 @@ -1163,11 +1216,11 @@ importers: version: 19.2.8(react@19.2.8) react-email: specifier: ^6.9.1 - version: 6.9.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 6.9.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) devDependencies: '@effect/vitest': specifier: 4.0.0-beta.94 - version: 4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10) + version: 4.0.0-beta.94(effect@4.0.0-beta.107)(vitest@4.1.10) '@react-email/preview-server': specifier: ^5.2.11 version: 5.2.11(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -1188,7 +1241,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) packages/ui: dependencies: @@ -1209,13 +1262,13 @@ importers: version: 1.1.9(react@19.2.8) '@tanstack/react-form': specifier: 'catalog:' - version: 1.33.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.33.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-pacer': specifier: ^0.20.0 version: 0.20.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-store': specifier: ^0.11.0 - version: 0.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) beautiful-mermaid: specifier: ^1.1.3 version: 1.1.3 @@ -1230,10 +1283,10 @@ importers: version: 0.17.0 lucide-react: specifier: ^1.28.0 - version: 1.28.0(react@19.2.8) + version: 1.31.0(react@19.2.8) prosekit: specifier: ^0.21.4 - version: 0.21.4(@shikijs/types@4.4.2)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14) + version: 0.21.4(@shikijs/types@4.4.3)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14) react: specifier: 'catalog:' version: 19.2.8 @@ -1270,16 +1323,16 @@ importers: version: 0.4.3 '@noble/ciphers': specifier: ^2.0.1 - version: 2.2.0 + version: 2.3.0 dompurify: specifier: ^3.2.6 version: 3.4.13 effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 happy-dom: specifier: ^20.10.6 - version: 20.11.1 + version: 20.11.2 legid: specifier: ^0.1.4 version: 0.1.4 @@ -1333,8 +1386,8 @@ importers: packages/web-shared: dependencies: '@effect/atom-react': - specifier: 4.0.0-beta.66 - version: 4.0.0-beta.66(effect@4.0.0-beta.103)(react@19.2.8)(scheduler@0.27.0) + specifier: 'catalog:' + version: 4.0.0-beta.107(effect@4.0.0-beta.107)(react@19.2.8)(scheduler@0.27.0) '@epic-web/client-hints': specifier: ^1.3.9 version: 1.3.9 @@ -1358,22 +1411,22 @@ importers: version: link:../utils '@tanstack/query-db-collection': specifier: 'catalog:' - version: 1.2.1(@tanstack/query-core@5.101.4)(typescript@6.0.3) + version: 1.2.2(@tanstack/query-core@5.101.4)(typescript@6.0.3) '@tanstack/react-db': specifier: 'catalog:' - version: 0.1.95(react@19.2.8)(typescript@6.0.3) + version: 0.1.96(react@19.2.8)(typescript@6.0.3) '@tanstack/react-form': specifier: 'catalog:' - version: 1.33.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.33.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@tanstack/react-query': specifier: ^5.101.0 version: 5.101.4(react@19.2.8) '@tanstack/react-router': specifier: 'catalog:' - version: 1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8) effect: specifier: 'catalog:' - version: 4.0.0-beta.103 + version: 4.0.0-beta.107 react: specifier: 'catalog:' version: 19.2.8 @@ -1398,13 +1451,13 @@ importers: version: 19.2.4(@types/react@19.2.18) '@vitest/browser-playwright': specifier: ^4.1.10 - version: 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10) + version: 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) typescript: specifier: 'catalog:' version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) vitest-browser-react: specifier: ^2.2.0 version: 2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) @@ -1421,8 +1474,8 @@ packages: '@aria-ui/core@0.2.1': resolution: {integrity: sha512-EEh2mIHL4HnfjIgZocxhCCqPRU6EE5eLFQcGw4FOfomil11jIBKtmqCQkz8qJ9Ni3IBoh/5DvUFaY206X0oY6A==} - '@aria-ui/elements@0.1.12': - resolution: {integrity: sha512-NnYKMCpUoSZYUSSXYrWOSiLih67iptxc+kLfrHbPW1BG1aFz9Bmofb2rljE7mQzU1iA5fUzPIFC0tgFH8DUoCA==} + '@aria-ui/elements@0.1.13': + resolution: {integrity: sha512-+uZaWhNobVCIncKn6wBsnVstWet9IM/W7zmDVk71Ktui4ZNRGSj9tudwnsQdnIPtIqDLHiUIr03NPCEnf99lBg==} '@aria-ui/utils@0.1.7': resolution: {integrity: sha512-ZKc/JOugSEYqsPUHYomxSbLyK9TcypsnhGL/yK2X14q6qyXVax5vHjLqgZmX5pwm28vYP4FOsUiUUtuSIK/eKw==} @@ -1433,10 +1486,10 @@ packages: peerDependencies: typescript: ^5.0.0 || ^6.0.0 - '@astrojs/cloudflare@14.1.7': - resolution: {integrity: sha512-o8fRJLeGfVW36AQ50JNcdUxfeUszWPXYGu8NA5PiC/wg2YetX04MdqEAda8Tp3rViYYpCJLoCOduIw0/enNtag==} + '@astrojs/cloudflare@14.2.1': + resolution: {integrity: sha512-WUQwugRg23uU4OCHEMDazqt44ZlUcnA+kLSd2bvNqoHHctNAKt893gr2JuAzUSzFZ6BuEOoun/zJiTMm+AiSug==} peerDependencies: - astro: ^7.0.0 + astro: ^7.2.0 wrangler: ^4.83.0 '@astrojs/compiler-binding-darwin-arm64@0.3.2': @@ -1513,8 +1566,8 @@ packages: '@astrojs/internal-helpers@0.10.2': resolution: {integrity: sha512-yt7fMgPYqSM4Tmr+taTW6Per+hjJ8Pk6lA1PAcDyqzOt8HzJ6Kje5WzCxA2Sd+9wsUW7uhkLeoTMK0cXPwH9rQ==} - '@astrojs/language-server@2.16.13': - resolution: {integrity: sha512-ekOa+CYprEq5n4EJC1qTIAhLk49HZIUQuFwrEuF+3JK/pdMaYnWoREFUI2A0KEPOJiFA2kamBzKzbYljDvUxLg==} + '@astrojs/language-server@2.16.14': + resolution: {integrity: sha512-YPXkBu6N4d1sT09pvBmIDGZay+1MemV551FSgdEM3aZRDzbkxd2H7Cvf8MsVJLVB1mIEyTf1XbMN/30gp7s46w==} hasBin: true peerDependencies: prettier: ^3.0.0 @@ -1549,8 +1602,8 @@ packages: '@astrojs/rss@4.0.19': resolution: {integrity: sha512-e+z5wYeYtffQdHQO8c2tkSd2JEBdAuRXJV4ZEU5IxkYeE6e39woDd7nw1PH1Kk2tEYNCYuKdylnnbhGmt61awA==} - '@astrojs/solid-js@7.0.1': - resolution: {integrity: sha512-Q1pBsferyLkw4yoJtgpbnu1/HSIOCx3QCoH3FAqNLZ/JnJpzQVPaT6aL/6yB67irxYThM5UA81hZCP5eVDAyyA==} + '@astrojs/solid-js@7.0.2': + resolution: {integrity: sha512-4Sk28a4HId176eXqEIrkuLmd+VFePwGSS/smL0ZdiUlb8cBAm5E5Gg0iSQIy/7bYuoj2xN398TcdtSZLl4regA==} engines: {node: '>=22.12.0'} peerDependencies: solid-devtools: ^0.34.5 @@ -1563,90 +1616,90 @@ packages: resolution: {integrity: sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - '@astrojs/underscore-redirects@1.0.3': - resolution: {integrity: sha512-cxnGSw+sJigBLdX4TMSZKkzV6C3gMLJMucDk2W+n281Xhie68T2/9f1+1NMNDCZsc5i0FED7Qt5I10g2O9wtZg==} + '@astrojs/underscore-redirects@1.0.4': + resolution: {integrity: sha512-sD3bn7i2yC+ocxlhCTshdg3gUM471Nag3wgyHilA+kpmufokvmUu1p5Mw5GT5Xp8SPC1Nlq0BJAgV/lslnWvRw==} '@astrojs/yaml2ts@0.2.4': resolution: {integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==} - '@aws-sdk/checksums@3.1000.26': - resolution: {integrity: sha512-CGznePoL+1oWCSzqmkvlYMpWQEZohPR3LntjKXXfVH+oh6Kd8d+yHLkjpiAGwPIHAxFe4OAq3aKfRnv/wqWIyA==} + '@aws-sdk/checksums@3.1000.27': + resolution: {integrity: sha512-insWOqKKNUrbN/dohEG7BJ0U5GkyqhjbMb/NHNaLUtq+7my2M8C4EnZZZoxMmXRqCC+P9dEr+KyJA2JGGzoKLg==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-s3@3.1103.0': - resolution: {integrity: sha512-FO7SB2vLhZRN3lBHVhMhRm3YKSHK8e917qjB8LMEEhU69cQ0Ahd/OMyezu+KqNANqEtyxfSnFVvYt81x6ZKGZA==} + '@aws-sdk/client-s3@3.1109.0': + resolution: {integrity: sha512-iPWzBeGkAe5H5+dBBGCOdIT4uMhpu12OK+nFnDzjvOChVnHIws4LeoG7Yl0kJHSRWZnNEkVcF4vQYTJny0e5xA==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.977.6': - resolution: {integrity: sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw==} + '@aws-sdk/core@3.977.7': + resolution: {integrity: sha512-I88Iov89NVmjSmJLKSv7Cn9M2J+a2942OkA8nZCbz+sl4ZeY4zEOcoLOrbt1GRfQ8zEQKnjAJdXixA3J/p1fDQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-cognito-identity@3.972.66': - resolution: {integrity: sha512-i4HTkP21eHaXA+XKoc6dYxlKPvYUqbusGGIsFRcSGTF8ln/q6RrWoRdVJ6UVODTCW59Ot1mVcbJXRAEf6kxa3w==} + '@aws-sdk/credential-provider-cognito-identity@3.972.67': + resolution: {integrity: sha512-0NKxn0U3I9qAxou30RlaezVnrhOzCYOkBqBethp8PmHJ7f1dep6AVMQVTrGUchB8bRjubi3MiVVql9+lyDOzBQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.67': - resolution: {integrity: sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA==} + '@aws-sdk/credential-provider-env@3.972.68': + resolution: {integrity: sha512-2a20A/IdNOwUvaDq91iqqS7BA0XlNMfW3iLGZGZLJv0EbUqhSxB0PIx4rQQqssvWj1uXImb3/UCCdHz/+1dOiA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.69': - resolution: {integrity: sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA==} + '@aws-sdk/credential-provider-http@3.972.70': + resolution: {integrity: sha512-0yRem2Fs52r/Nn6UAqIlpjexfaYj8ziEozOe9tamtAVT/5bzFLKx8O2r7MaRqgS3hGKHIa1Jij9nKHSsNnb04A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.973.12': - resolution: {integrity: sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg==} + '@aws-sdk/credential-provider-ini@3.973.13': + resolution: {integrity: sha512-2M39DE02XpYYaSWYk/4AsImXYUU/1L2xmTMLUpMMWq7DfLv191/vCRy3baKtdr45AkJQyVgSjmuVOLm15SwrRQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.74': - resolution: {integrity: sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g==} + '@aws-sdk/credential-provider-login@3.972.75': + resolution: {integrity: sha512-jaTESuJlQsoUZ44f/i2puyPt8VlF/dMMJ9HM3cStYtk7eKX4N9UWi83OLixUkoOJH3BwWlPLCq9YIK9nfWhVBg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.78': - resolution: {integrity: sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng==} + '@aws-sdk/credential-provider-node@3.972.79': + resolution: {integrity: sha512-RIw5dof1EHkWubrZzPC941CDtnFG1iAXsxbFgLkhdYZXHc4icU13c/uxSMI0J5eUx9bxa7LjfpdjfClBB1QsDA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.67': - resolution: {integrity: sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q==} + '@aws-sdk/credential-provider-process@3.972.68': + resolution: {integrity: sha512-nLP3Pda2MQTFJ25hKBMmUuB9Uv+bTZQNlufbeCwklP549Vwnkd8bRLJoCKp5k6xjmdyptrPrOfGOhN0mKuca8A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.973.11': - resolution: {integrity: sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw==} + '@aws-sdk/credential-provider-sso@3.973.12': + resolution: {integrity: sha512-EmgyyHn+f9WCcelp3L/vci+LGbX8GigWaVphRArjVo5Pktkr9YnLy/mQ6VDkDyBD72dtfRNTgHmD2ts4rTDXKQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.73': - resolution: {integrity: sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A==} + '@aws-sdk/credential-provider-web-identity@3.972.74': + resolution: {integrity: sha512-0YfczxGXF3RjGj8z7QG/Ho2HnLGKDHfPSHiTs47UU1U/+mmwISDN+rvGKt2zh+3FX8NdT4xd95LGBGyhQw2dgQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-providers@3.1103.0': - resolution: {integrity: sha512-ZPJYLJx6v8YZOgsoe3Ijr6u0rGq/pvwD4BwIl2yhJgqeIdegsaImuG+EYGbh7ujqvnDiawAmxmnKa2UlDyRdNA==} + '@aws-sdk/credential-providers@3.1109.0': + resolution: {integrity: sha512-F6s4maJt0q/g5v8Js3GQV1zt/vmccWW1ScpsrqKd61zIMMLc6iQa4ywHrRViPWgEbPazM9XhENk6zffr7sFaLQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.72': - resolution: {integrity: sha512-lSAoVPvQxX1d8TOM6waKDBQrvvZcm4w6pCldFAsRUffEaXq6lYY0pPyew3KlLu6Xqb74DXI42hGvSsbGBLljlw==} + '@aws-sdk/middleware-sdk-s3@3.972.73': + resolution: {integrity: sha512-oy7sRA5HvHcAvkcKX6F8RI240jcOf3c8y/Gqjs9qemIibdKQqGBIi0uwa+47ZRYqGLpdEO28TQU4G73yUzo06Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.41': - resolution: {integrity: sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ==} + '@aws-sdk/nested-clients@3.997.42': + resolution: {integrity: sha512-XWRyon2MTHXD/zMoo0Mbge6Vwf+iE0qQaM/RyGO6NfZ9WukCFiQL27nQVZjYy2JwSIg+iXZxKOX95OBXqlSM4w==} engines: {node: '>=20.0.0'} - '@aws-sdk/s3-request-presigner@3.1103.0': - resolution: {integrity: sha512-937UiasYudfKWsCU/+kAHJ4UMJa90l0UcMDLwPvZ2UBjhJ4Iz5mVxiZNowthsPmJ8wH+tHqTl/O1kOD6dMuIIg==} + '@aws-sdk/s3-request-presigner@3.1109.0': + resolution: {integrity: sha512-tVoeEJ1sERyMrzmFnE1blsKrwg40xzAPgrlk4dlqAUN317GeJAD1hUcY3kciqTH7T/QvxVr6PE5iQAaASnPWRg==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.43': - resolution: {integrity: sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==} + '@aws-sdk/signature-v4-multi-region@3.996.44': + resolution: {integrity: sha512-ZSfQ35Qn4MhSY+A0Whyr+KBx+wJKZUyBsOrjB2pSHOafRzbFe47T8XcXM8hZqUAC69qnqIy0C9ArxTuud0CC2w==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1103.0': - resolution: {integrity: sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ==} + '@aws-sdk/token-providers@3.1108.0': + resolution: {integrity: sha512-rI80zxDxGJ6904eC/YbjkdjY6JdaZvQ01kOmrMvw7cFQGIHo27fhnIVbMSVDS4T6foQImjxYSRoOu/uSJscXDw==} engines: {node: '>=20.0.0'} - '@aws-sdk/types@3.974.2': - resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} + '@aws-sdk/types@3.974.3': + resolution: {integrity: sha512-ECAqfpNsef+7MO8qtR0h9KcFIBAygaE7Cm6UOiQl+ft+uVap+1G7bNEjs4mdJE2OnA4m6k7i8peH8uGIAsOMGw==} engines: {node: '>=20.0.0'} - '@aws-sdk/xml-builder@3.972.37': - resolution: {integrity: sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==} + '@aws-sdk/xml-builder@3.972.38': + resolution: {integrity: sha512-grf7mzfVxBS5AlsuTvBN7uDpzqohFww9fRPCO+EBSUdvtsYMcPSKdz54h/7XiscqNcUM1Ae1MF7JLHmiYYuzbQ==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.3.0': @@ -2063,12 +2116,12 @@ packages: workerd: optional: true - '@cloudflare/vite-plugin@1.50.0': - resolution: {integrity: sha512-zIhZim7Kr7OC22rlOkU81BLj0oRlUOqPRX+E6RU3hyRYg4xU1MbA3yiMIwFONr+jc/uV7Fxs20NlXrB89Fqjqg==} + '@cloudflare/vite-plugin@1.52.0': + resolution: {integrity: sha512-uQ7bdlmR7ZbEHa77BuolIJIOVBaM0VCwxCnkgx+E/YMtwGdKH43rTpL9OZ1GJzqYvk6xfHyoZpqqfLXHPnD7Zw==} hasBin: true peerDependencies: vite: ^6.1.0 || ^7.0.0 || ^8.0.0 - wrangler: ^4.118.0 + wrangler: ^4.122.0 '@cloudflare/workerd-darwin-64@1.20260730.1': resolution: {integrity: sha512-+MBHmPaiTe2KajryW0T24rZvWFxb41hD3d8anNzQqHzft6vSEb18+sp0znSwxgij7ApPhSM1+vhkNg4f3YMguA==} @@ -2076,30 +2129,60 @@ packages: cpu: [x64] os: [darwin] + '@cloudflare/workerd-darwin-64@1.20260811.1': + resolution: {integrity: sha512-i5jqz+ywtOefr0AJbiAc8qxBLfSim/B0WJG7aW3B+pWnoVfMJdUQvi+BWcFKZJ0MoCci3KadTx6g31VfuEEqpQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + '@cloudflare/workerd-darwin-arm64@1.20260730.1': resolution: {integrity: sha512-SBHKntPkKvNPgaCrTe99xC1CAl8ygJDzlYfK0LbuJ1muKadIw35WnhO0wu894fKBtllsVQdNzDLee+cm0ppLSQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] + '@cloudflare/workerd-darwin-arm64@1.20260811.1': + resolution: {integrity: sha512-NoOUM/nvaDdm2Onlnz33FikWjtatzulNtvwvy4xs0IrHaTCHwC0c8NwIt6s+AI13FkDs02/vm2I3GTPLCT9+hQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + '@cloudflare/workerd-linux-64@1.20260730.1': resolution: {integrity: sha512-ouyPOSMbiKPeSwUJUvxtMcxGAXs2J4aPE4T5ABIYX5ClcQx5j5bbHTmnqOQEY8sAuLTPjH7dY+iB6UI5ISlwwA==} engines: {node: '>=16'} cpu: [x64] os: [linux] + '@cloudflare/workerd-linux-64@1.20260811.1': + resolution: {integrity: sha512-sdYq2jL1AD1supa3fsi5O4zTB28wSjvTHj7Migh6/ts8EROPdvrSwv+rdGHhv8HJNAz/wbIAY3wZsi1Rw4uUIg==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + '@cloudflare/workerd-linux-arm64@1.20260730.1': resolution: {integrity: sha512-YQ+Mi78U3TPdgBPtwq+Sm6rJU+Ihl2y0pjYtuuKkdmUbYzL7oLR6Xqq9wljhasnuCFICssDJaqhMep5WizYoEQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] + '@cloudflare/workerd-linux-arm64@1.20260811.1': + resolution: {integrity: sha512-RIRv4shbu1kg05sD+DHTpSFCNnb5Dl2SkPDMUykqZa508tkPqe7VVw7gO0Q5msTBGyL0FfFrLuRxwwfA8u5Sow==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + '@cloudflare/workerd-windows-64@1.20260730.1': resolution: {integrity: sha512-27fAN+vUECW1oYVc1KOcHYpkL8COM2Uxtxql7TL595kxbjoqS5yckw7NLz7bTf2pALFCZWjqXDjZGJ/xbG4ZKQ==} engines: {node: '>=16'} cpu: [x64] os: [win32] + '@cloudflare/workerd-windows-64@1.20260811.1': + resolution: {integrity: sha512-g6VquwjASlYAibcNW/0E6Zszht4qLkmnXOGwIjjRHl2A0Qz48kVeMcGvyH6eA0G9U3OzZojjYFpP+YeyQmmdjw==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + '@cloudflare/workers-types@4.20260702.1': resolution: {integrity: sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==} @@ -2110,6 +2193,16 @@ packages: '@date-fns/tz@1.5.0': resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==} + '@distilled.cloud/core@1.0.0-rc.4': + resolution: {integrity: sha512-g/5THnVZoBKO31hhdU1JJxqcI5aXAmMIJQpv2WUmutYeedbdX/kknnvwY5a75rc7MGSWsHnB4EAA/07FtdSSKg==} + peerDependencies: + effect: '>=4.0.0-beta.104 || >=4.0.0' + + '@distilled.cloud/github@1.0.0-rc.4': + resolution: {integrity: sha512-tLPbetotFD90i69LWzshI035wkAYQumfYo8QjcTASXROiW4CE8ogVzpJIrddFDIkOFbXLvamuH6Y62jXiuMu4Q==} + peerDependencies: + effect: '>=4.0.0-beta.104 || >=4.0.0' + '@dnd-kit/abstract@0.3.2': resolution: {integrity: sha512-uvPVK+SZYD6Viddn9M0K0JQdXknuVSxA/EbMlFRanve3P/XTc18oLa5zGftKSGjfQGmuzkZ34E26DSbly1zi3Q==} @@ -2157,44 +2250,50 @@ packages: '@effect-aws/client-s3': ^2.0.0-beta.6 effect: '>=4.0.0-beta.66 <5.0.0' - '@effect/ai-openai@4.0.0-beta.66': - resolution: {integrity: sha512-jhLwvQhyRELKXkSpKFrwGztzCp8ZbQZsdXxVyU5AyxMV2WfkclQRmmHG0RsI//oyYq4Pn6gEw+9rN8YaK/G42Q==} + '@effect/ai-openai@4.0.0-beta.107': + resolution: {integrity: sha512-hBk19LFukdb3yMOoNUHZ+G7RDzCqxfJIRdLM3I/quXb8szgYBAGTFMBnMzrUdArofn4av3K00sLnASKyhPFvoQ==} peerDependencies: - effect: ^4.0.0-beta.66 + effect: ^4.0.0-beta.107 - '@effect/atom-react@4.0.0-beta.66': - resolution: {integrity: sha512-YEUbGXBZsb9dmgQ/FMTsqU6IYp/2rRVqloySSXFzhXlIbRT6b28SvNyTpK3x+HLGavURYfTUjjZ/Y2wEj/7A9g==} + '@effect/atom-react@4.0.0-beta.107': + resolution: {integrity: sha512-dSx8Mmgge8oy3On8k95V3dyaRo9od8Eg0our56PIrTlRZBBRLXM2vc/X7GsxAYtHs2PMracVFUJqizXCcPsCyQ==} peerDependencies: - effect: ^4.0.0-beta.66 - react: ^19.2.4 - scheduler: '*' + effect: ^4.0.0-beta.107 + react: '>=19.2.7 <20.0.0' + scheduler: '>=0.27.0 <0.28.0' '@effect/language-service@0.85.1': resolution: {integrity: sha512-EXnJjIy6zQ3nUO/MZ+ynWUb8B895KZPotd1++oTs9JjDkplwM7cb6zo8Zq2zU6piwq+KflO7amXbEfj1UMpHkw==} hasBin: true - '@effect/platform-node-shared@4.0.0-beta.103': - resolution: {integrity: sha512-0aCZMBid5ifqmY55TkfCDLaGTIM8qu3bNFUW7qL9vh/7jFOkaIAMX2MA8muG4deqW17XWxawddWu4v0fK+UW3g==} + '@effect/platform-node-shared@4.0.0-beta.107': + resolution: {integrity: sha512-y6BqcRi86BfTJv+tvDrob4ozYVHxxlHYcn/zIQqZjXI9CvKnkgD6ng+38G1o45c4f2ucU+6HRI9POCmFdMoVGA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.103 + effect: ^4.0.0-beta.107 - '@effect/platform-node@4.0.0-beta.103': - resolution: {integrity: sha512-VD8fbpendwFokMwzC7/MxazjhEVDihPC5NZtomYEyZwGaA1LXMvwP1y8pfXwfshUkG56TN4EMQqzu72c0B9FhA==} + '@effect/platform-node@4.0.0-beta.107': + resolution: {integrity: sha512-k+6YNbV4Ck0L6YXtlgkvEnuP5tlxWD8EeWOrpn46PDqbGEwt4ONpRltTwm3tn2cyBXD0i+2P11cUH/6sdFagTA==} engines: {node: '>=18.0.0'} peerDependencies: - effect: ^4.0.0-beta.103 - ioredis: ^5.7.0 + effect: ^4.0.0-beta.107 + ioredis: '>=5.7.0 <6.0.0' + + '@effect/sql-pg@4.0.0-beta.107': + resolution: {integrity: sha512-y5RWMhdLhFqn0picXqZIR4Bevo4Fo+aT2xHNiyZoAR86d8CnbXp6Agq9HmXIGWS/nd5f7Bs4d7Aif7QUTsUofg==} + peerDependencies: + effect: ^4.0.0-beta.107 - '@effect/sql-pg@4.0.0-beta.103': - resolution: {integrity: sha512-jzlTFtFaaoOmT87rQMKUBhkFeIQQwDKtlB+MOYn1jIPssLvXZVnMqo9GahCjsNcXVlB6QGBkUgD1HCA6+tJvBw==} + '@effect/sql-pglite@4.0.0-beta.107': + resolution: {integrity: sha512-FhPZbhIB86crgHWUE7nybZ8WvSqEZ8tqQYSmQCIMWkOmo1RHfEkXFBtctCix0AEbVhswTOz41bi7V9TZPNmYnw==} peerDependencies: - effect: ^4.0.0-beta.103 + effect: ^4.0.0-beta.107 - '@effect/sql-pglite@4.0.0-beta.103': - resolution: {integrity: sha512-/a8I2rjfW6/0/Tvr70BTrRXXMLUQXbVDamhmnN97l3krWq1br+IH2ltbYn+358pJ+GJ+6UQ+eAR/P10WIs5anQ==} + '@effect/vitest@4.0.0-beta.107': + resolution: {integrity: sha512-n4/qsx4DnT4dEI/wNgMivxyUeJoeiU1TCSz0WnoHWk/dny40Oxjip2P9IXGQDgPb9fsYVnerF0QRA6nPUuExQA==} peerDependencies: - effect: ^4.0.0-beta.103 + effect: ^4.0.0-beta.107 + vitest: '>=4.1.0 <5.0.0' '@effect/vitest@4.0.0-beta.94': resolution: {integrity: sha512-pCjcUMTBizn2wibiyP9Tl3VvGEkplvIA33iCvmK3y/toi54IWE3PNL7d0JkZvEaKV2OoaKVTaH1dOybRtpBuQw==} @@ -2278,6 +2377,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} @@ -2296,6 +2401,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} @@ -2314,6 +2425,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} @@ -2332,6 +2449,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} @@ -2350,6 +2473,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} @@ -2368,6 +2497,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} @@ -2386,6 +2521,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} @@ -2404,6 +2545,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} @@ -2422,6 +2569,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} @@ -2440,6 +2593,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} @@ -2458,6 +2617,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} @@ -2476,6 +2641,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} @@ -2494,6 +2665,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} @@ -2512,6 +2689,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} @@ -2530,6 +2713,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} @@ -2548,6 +2737,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -2566,6 +2761,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -2584,6 +2785,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -2602,6 +2809,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -2620,6 +2833,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -2638,6 +2857,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -2656,6 +2881,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} @@ -2674,6 +2905,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} @@ -2692,6 +2929,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} @@ -2710,6 +2953,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -2728,6 +2977,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@faker-js/faker@10.5.0': resolution: {integrity: sha512-bsxD8WLS5lIj7aaoCx1YJkktqYj5vlBUE6HWzu2Q51ksrGJ0H737ECCKlFU7Yf8Br45z9t99frBp/J7kzbMPAg==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} @@ -2764,8 +3019,8 @@ packages: '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} - '@iconify-json/lucide@1.2.121': - resolution: {integrity: sha512-zSoQQSmsyFaeTpSwURHJ9PIrsDcGFpDNhGlpiTrs+Ua4uFeH5UsE26L4OEBLrgLWoSK0UO+JhsO8yehSw0403g==} + '@iconify-json/lucide@1.2.123': + resolution: {integrity: sha512-0CozmpKXEOEEhltrfT2zt+/t1hez67Y+hM2VJWoIcBKdBrb83VYuKf+b5A0QU9HfQm+RNn2GjFWlTOwi+Ss6IA==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -3247,8 +3502,8 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - '@inlang/paraglide-js@2.23.1': - resolution: {integrity: sha512-KEnJhvRLhB1zbGNmt2DHi4SuIbr7ZcZ0tftFYxQDDSpS1GqjJJ+b+ZaxVC2PElpgcQYNgD5Bt9hYUpV+et8IMw==} + '@inlang/paraglide-js@2.23.2': + resolution: {integrity: sha512-tzWnZ6DEQ3JRxpSM/lsmfUFSNXLymvlfUei4uX1lo/Qh3a8OfxZ5nchGrEM+p0aLX4m5ZMrVuMKfaD0qXJFeoA==} hasBin: true peerDependencies: typescript: '>=5.6' @@ -3359,8 +3614,8 @@ packages: '@lix-js/server-protocol-schema@0.1.1': resolution: {integrity: sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==} - '@marsidev/react-turnstile@1.5.5': - resolution: {integrity: sha512-+EtXbmsJwlRuIUruLb3UBAakpW0wFGeA17QQp0uG8Mv+Zh8MXktZT68P5HS2g1ssfeRd2+BZGupOE/F4KGajFQ==} + '@marsidev/react-turnstile@1.6.0': + resolution: {integrity: sha512-T2Um71ZdBgQBiyS01xgRMvDWk+mEsrLtXLFVhOC917OVEO584wbPOWj1sw35RaM17+q9QoADgLVcH60xr9NRqQ==} peerDependencies: react: ^17.0.2 || ^18.0.0 || ^19.0 react-dom: ^17.0.2 || ^18.0.0 || ^19.0 @@ -3425,12 +3680,12 @@ packages: os: [linux] libc: [glibc] - '@napi-rs/wasm-runtime@1.2.2': - resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} + '@napi-rs/wasm-runtime@1.2.3': + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 '@next/env@16.2.3': resolution: {integrity: sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA==} @@ -3542,12 +3797,12 @@ packages: cpu: [x64] os: [win32] - '@noble/ciphers@2.2.0': - resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} + '@noble/ciphers@2.3.0': + resolution: {integrity: sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw==} engines: {node: '>= 20.19.0'} - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + '@noble/hashes@2.3.0': + resolution: {integrity: sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==} engines: {node: '>= 20.19.0'} '@nodable/entities@3.0.0': @@ -3682,8 +3937,8 @@ packages: '@oxc-project/types@0.126.0': resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} - '@oxc-project/types@0.142.0': - resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} + '@oxc-project/types@0.144.0': + resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -3724,11 +3979,11 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@posthog/browser-common@0.3.1': - resolution: {integrity: sha512-1nhMVY1wnHADTg8tR9yvm+lPAz5ROxznfQlBtLzB2FFo4lp/LU8lk9KyFPsARDkBxCfYachsJfvRjocL1G/AJQ==} + '@posthog/browser-common@0.5.0': + resolution: {integrity: sha512-8DaxVZS1bQPbA514RePurLNbYjei3P4jhnC206DwVv5XThmZM3QdlsXenI2ujE3pLbgQ79hYn9o1Kda8I3WK/Q==} - '@posthog/core@1.46.7': - resolution: {integrity: sha512-Oxsa6AvXXtNhHI+BDMBvysSg9lPIrMKWP9mKKgqD9M0jgyZN1GuIxIXI3T+0h9kLRWdIFFIOGaNNrCmFTg6Dzw==} + '@posthog/core@1.47.1': + resolution: {integrity: sha512-d38C5DulL3gCox4g0VGyb/Lhn548fBvj9f35LZGVasPspOs3jyApxROJyDXoWwIRivOjCOIShZQAdYkZVn9J3w==} '@posthog/react@1.10.3': resolution: {integrity: sha512-Qu//fGQmVlX0B9kTA3LLg67e7AYLEmeuA0Bf1qSyUM0uUILcRQGjQezhNQPLYSTakOqvXEnl6fM2iQBF6Toxrw==} @@ -3740,8 +3995,8 @@ packages: '@types/react': optional: true - '@posthog/types@1.401.0': - resolution: {integrity: sha512-tLGiHJhYoGMFkcB5kPT3eTOMAKQYd8Wsm2H+RvMkDehY2YJwLjr2wrJ3DcRddpdivqT0TYT9TAMWKSNRzNSMtg==} + '@posthog/types@1.403.0': + resolution: {integrity: sha512-QbkO0epmdq38xhxwP214YRi0vgpZiXqhamaTjKT6kVGJYTtsE5qZ1GTgLp12IYehg/rd+whYYErH3/DeX8pj3Q==} '@preact/signals-core@1.14.4': resolution: {integrity: sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==} @@ -3887,8 +4142,8 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.2.2': - resolution: {integrity: sha512-l7x215OGvo1s52JWmR8U/DAVzEDWBCIbTm28aeJV/WDTSHgcKXaZTuBT0hJMs5NggilfJTW3clZVvd24yfKJxA==} + '@rolldown/binding-android-arm64@1.2.4': + resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -3899,8 +4154,8 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.2.2': - resolution: {integrity: sha512-9u9Xv6c1AJZT0FfwH5vrMG5Jjcwhc1MlyrPu0XfTqkzsmqfks2M6W/o5XwAJgVVN/jHpqqngC1WevHKKTIUtIA==} + '@rolldown/binding-darwin-arm64@1.2.4': + resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -3911,8 +4166,8 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.2': - resolution: {integrity: sha512-9W1mbGZAfW3oqd85bhBkmpyHCCzL1TeG/zFFP3vg7b0rlly8cxOcre5nXwz+LHazCwac2MNWgPdPCHndABjpWQ==} + '@rolldown/binding-darwin-x64@1.2.4': + resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -3923,8 +4178,8 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.2.2': - resolution: {integrity: sha512-0p1lhiCSCyaerFwtrdZQUx7NqGk6LQnaRKWX7tFQqwQgvX0rjM15cIkm3pax1UpEakK14C4mOxx/jSqCBdBRqQ==} + '@rolldown/binding-freebsd-x64@1.2.4': + resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -3935,8 +4190,8 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.2.2': - resolution: {integrity: sha512-e+cOJXrJ2L3zx6YzqPg+f6Wbk3V1cKB8bOhbaYdVYN3DdquzNdRAmrbETz1qnt5yp/c7JNlNjmITiA2cVneQ7w==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -3948,8 +4203,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-gnu@1.2.2': - resolution: {integrity: sha512-JsSMsj6sNat/MuhG5fnBD7QgbtpHKVe30x5/bAVirDHdhoQRXJkF6xc0Jqk8O4fiCUQAzMOoH9wZi3m60c8wtg==} + '@rolldown/binding-linux-arm64-gnu@1.2.4': + resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3962,8 +4217,8 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-arm64-musl@1.2.2': - resolution: {integrity: sha512-B5G/zJdHaoJn9vD50eGHWkiWfmq8Uhi3IiLPJTzmZTrAalk1bztUikSXo0qga18ibE0IXboyeMUnhPjhAJ45wQ==} + '@rolldown/binding-linux-arm64-musl@1.2.4': + resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -3976,8 +4231,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-ppc64-gnu@1.2.2': - resolution: {integrity: sha512-6mC/awzKka8W6EoekjegpfGkjz8jXWDX63pqu/HYVpyKtZfu65Jsh4QAH3Kej3CAv/c1oGX7psTmFEbr0mDxLA==} + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] @@ -3990,8 +4245,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.2': - resolution: {integrity: sha512-412MX9fJLdA1IK28EZnc8jYv2HRTleOZgfLQumJ5zy7OeJLZlg/CETwFaXjNmGVxG51cFHpKLqb5LKvBC+HsHA==} + '@rolldown/binding-linux-s390x-gnu@1.2.4': + resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] @@ -4004,8 +4259,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.2': - resolution: {integrity: sha512-Q/+HI/ToJafZ1iCqGgVQXUEkIjufHCTF0gBQ2a5o3cg7GJ2h0qyq3nvvSmU+bGda2/7ygXpTY4TM6gO9OhQ0ZA==} + '@rolldown/binding-linux-x64-gnu@1.2.4': + resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -4018,8 +4273,8 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-musl@1.2.2': - resolution: {integrity: sha512-ZKp/w41n6wCvxzxQHtQSbuphfX3Y4cCvbjkKHusrLx4lh+JWLTU7StSltO/DKARISzbj368d+qUaCjI8K2wzXw==} + '@rolldown/binding-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -4031,8 +4286,8 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.2.2': - resolution: {integrity: sha512-pxE6xD4KS3eAROkKK5yrhB9/3+vhlhVGMvlQLbdpzrBGDbKrnzx3RLwPaHvasLo6jgaiBLn7e04Df9C4tYhjmA==} + '@rolldown/binding-openharmony-arm64@1.2.4': + resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -4048,8 +4303,8 @@ packages: cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.2.2': - resolution: {integrity: sha512-4MqEue5re+xIZzAWsB8sj0P1kqZySWqIuN4t6QaIO/YA6SFwySOLruvWQFzfmqk8LBK2P30KCSJwf6mJCZZ5/A==} + '@rolldown/binding-win32-arm64-msvc@1.2.4': + resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -4060,8 +4315,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.2': - resolution: {integrity: sha512-NweNxxD0Nf9t8v7kodun45Ijp3EIwYY+uydPP6qBEYvfBqhIjN6dZMzlQja3tqX/aLs3F3Uz+AxDpKgRhpOZQg==} + '@rolldown/binding-win32-x64-msvc@1.2.4': + resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -4078,15 +4333,6 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rollup/pluginutils@5.4.0': - resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/rollup-android-arm-eabi@4.62.4': resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] @@ -4231,34 +4477,34 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} - '@sentry/browser-utils@10.69.0': - resolution: {integrity: sha512-e/u1Abj0zRPwR/deGZAP3GOULrsx67/XXnM5Skniqs4uxTsdNtPek1Nef0tpxwaQJYxwh6pWdhswLPPbbPOgBQ==} + '@sentry/browser-utils@10.70.0': + resolution: {integrity: sha512-IvjhafF5NFXrPCg5EHbAPGDwIhHxgUcLlHTklZ3DAdA6ky88BJYMfevbcnOEmgavf4nylhCaquRUFNB5+szj+A==} engines: {node: '>=18'} - '@sentry/browser@10.69.0': - resolution: {integrity: sha512-8391tnm96YbR7b8SYfEA/NEIZuyb2r3SZrtAT0bhZtjlujcYWjo7gugQvk8sWLU9cAa/euD00eJoIoJvNfpd7Q==} + '@sentry/browser@10.70.0': + resolution: {integrity: sha512-IK6+J+8H06tZe+A8L37TT5ZxxwNtyQatW8zl5RYYJ/e9CsjrM8fPi8I1OT7uquTw8UtjqFHt7bEef/Vy63ksPg==} engines: {node: '>=18'} '@sentry/conventions@0.16.0': resolution: {integrity: sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==} engines: {node: '>=14'} - '@sentry/core@10.69.0': - resolution: {integrity: sha512-+uuqVEeiDzYuAKjZLqsROKXvRTbl/QeH0gfGRtpYib1cud4rAFWRIkFmcR7Jb7JGFYwmReyQotiTj/hcDszTZg==} + '@sentry/core@10.70.0': + resolution: {integrity: sha512-ozhCTDqg89oB4XmWfAwuHshABpvT7AkRpaPnogopPfMAaI61G1t8EKCJ4W7aum8JSBonlfyjPCyW5oYZFm0KvA==} engines: {node: '>=18'} - '@sentry/effect@10.69.0': - resolution: {integrity: sha512-CQviTr3mkz9dfPii4kVadQBmn4kJmuHnNKuUEYb0/yv8gylPPPXJC6aDu/mDlw1gQAtdF/lloQxG3sQGpCEH7Q==} + '@sentry/effect@10.70.0': + resolution: {integrity: sha512-lDoQdDl25jeCEgXRyYk00emtTsIpIX51ecO/PdDt1Ju8AJOHOSbTKHFBxXSZ/ltORwMi+GJD7lNfz7cBU1w7xQ==} engines: {node: '>=18'} peerDependencies: effect: ^3.0.0 || ^4.0.0-beta.50 - '@sentry/feedback@10.69.0': - resolution: {integrity: sha512-qrGz5Qaw93/IhMjlFN6uIaXeHwgHDaKGa6FkTAP6PonpkvSbGGqan6xfsENxzj9HUVoli1lZ6tMRDnt2qtSPhg==} + '@sentry/feedback@10.70.0': + resolution: {integrity: sha512-6VQn2ETJjHkk4QQDdx/587/JDfXsk3yBTLZ3UZOMtBVrkmwgk+1FJZ8ULJ3ud1xZcuh2icunIkc7tGVv2axdnw==} engines: {node: '>=18'} - '@sentry/node-core@10.69.0': - resolution: {integrity: sha512-IgArHczrZJxkgxoffHscj0NxQrG6kCazgmGQnlf3j58J1ec21YaUu8Tu+7G4Lo5tCiW3teQnwlKW1ttMXSqWRw==} + '@sentry/node-core@10.70.0': + resolution: {integrity: sha512-oPOEVVNxv5WHtckx2i06Wi9FLWyvOg/1DUeX732jZ4iqT2nupINaMH4nF4f4kSvUThFnxkFSRQxwqOxgzMKhKA==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -4278,48 +4524,48 @@ packages: '@opentelemetry/sdk-trace-base': optional: true - '@sentry/opentelemetry@10.69.0': - resolution: {integrity: sha512-3FyWV6YcEJuvLrlaKGE1dHXCI+1YO0a62w7PkwlRg8yp6K6YXkmdwu9GjqaYD+Ju4tm7uC7mHIsGFQMm0M7pqQ==} + '@sentry/opentelemetry@10.70.0': + resolution: {integrity: sha512-UNV/2tqypcUK6FDzerAsFJn1Km/c4VZCYkUZDNbnV5S0cwAq2BYKMo4M5vovaLDBQlxA+Wk9ovbxi5wYjjl9fw==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 '@opentelemetry/core': ^1.30.1 || ^2.1.0 '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - '@sentry/replay-canvas@10.69.0': - resolution: {integrity: sha512-VF6nXvSninHcc7dC1Zme0RjkC7VgRMCixs6jKaQX5zTNeqTW3dZGSefSOVv+ZteRi3hJvVORq985VjUC9Z/0+A==} + '@sentry/replay-canvas@10.70.0': + resolution: {integrity: sha512-irzpw22bK5CF3jbecDa0gBUcfjv7tgeUoLAvtIfeHOP5ajmf3o4Cp99a9RQqkfgvkcMeRZBUwE91SQhp6Ank+w==} engines: {node: '>=18'} - '@sentry/replay@10.69.0': - resolution: {integrity: sha512-uRhmNhtFGPOlM0iniVmWKAX3KVXI0le41yYK/iKdPjinT9jA3ZrmykO/Fv1v/KI5znOtwa9D6eHRnDTTMRxFrg==} + '@sentry/replay@10.70.0': + resolution: {integrity: sha512-xMnSGzJn9Xd29rYd32lkx/gFW+5mtgqADJ2FiZvis0MBGZuDlNRwPn0/Cs1xA3JNXKV3NGfhdmmvs90w4dHSmw==} engines: {node: '>=18'} - '@shikijs/core@4.4.2': - resolution: {integrity: sha512-StyzbAyxg2/tBGf78gwbBkGyeQ73lf8UiJArFaQhTQIDqQOCKPCQFanvrs4/Yv3Yfyc+ONInJM6K+FMIf+P+kA==} + '@shikijs/core@4.4.3': + resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} engines: {node: '>=20'} - '@shikijs/engine-javascript@4.4.2': - resolution: {integrity: sha512-MnIkeqWdVPUWsxlx8gKLVCJFTsqrQJgpTPBPpQwaFeJ56lOnJxj5aN2LUFnfxUEcvOQuNocmbaMnVrCEln6rkw==} + '@shikijs/engine-javascript@4.4.3': + resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} engines: {node: '>=20'} - '@shikijs/engine-oniguruma@4.4.2': - resolution: {integrity: sha512-GLhowz1+jixjz+wiZ3wMnOn1jTxiFCGl2PkXufivbnwPHKuyw1AYqu5/hbWhZZ2oAb0NP05WUJhYeigY14drnw==} + '@shikijs/engine-oniguruma@4.4.3': + resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} engines: {node: '>=20'} - '@shikijs/langs@4.4.2': - resolution: {integrity: sha512-8DfeusD+Zdv/eYIDdXyJTUnSMHt+aAWjAOCXV20HNGAHRlInXpG8wh421v6B91WOm9TFwRLN+b/LG5F2NAIojg==} + '@shikijs/langs@4.4.3': + resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} engines: {node: '>=20'} - '@shikijs/primitive@4.4.2': - resolution: {integrity: sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA==} + '@shikijs/primitive@4.4.3': + resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} engines: {node: '>=20'} - '@shikijs/themes@4.4.2': - resolution: {integrity: sha512-H0CFoL07ddDC2Dd6EdrPYNkRhUR6YCkJlnuYFceYYUJJA5TIm2b5B33qqiDYryBExgbKMndFJPb2u1gTuqO37g==} + '@shikijs/themes@4.4.3': + resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} engines: {node: '>=20'} - '@shikijs/types@4.4.2': - resolution: {integrity: sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ==} + '@shikijs/types@4.4.3': + resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': @@ -4340,40 +4586,40 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@smithy/core@3.31.1': - resolution: {integrity: sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==} + '@smithy/core@3.32.0': + resolution: {integrity: sha512-NAiCSC78fzbNIEWoheoF74Ob5ZorLijCHpMY26Fqvqg/+9LuyIqMfHDg2p8Yk1rqOyowtiL3y7WX0AW+teL6zw==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.4.16': - resolution: {integrity: sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==} + '@smithy/credential-provider-imds@4.5.0': + resolution: {integrity: sha512-2jsPi+7Zv2hSzD9IXR9D7DTqSn7mv4XalzRm+bESh53jiaUS3NKEUbpQFTJP0HhQy9qzZvluxQ3yS24zdRrqsA==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.6.13': - resolution: {integrity: sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==} + '@smithy/fetch-http-handler@5.7.0': + resolution: {integrity: sha512-W/exA8T0LEzCQtJ02w4IzaEQPIspgarqZprb7W8FwnYiDowgCrjl2fTQ6FvuSSUnJORuepBF81abmBJwqh+0XQ==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.5.16': - resolution: {integrity: sha512-N4XTMCO77u8jXq9Ms1pbFM3M9zD+ScKnvB+M7//VyawM8L6xW3/mqAPp/uMOYprW+q/b1Qen6qUKVwxTb2ymiQ==} + '@smithy/node-config-provider@4.6.0': + resolution: {integrity: sha512-DXGmwJDsQzx033lbe5+mMnK88n9PK14akJb67GQFmxD7PXPa1+AbawssOZA8q9Y4Wp+6M8a9RKfFYYoRCdfcuw==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.9.13': - resolution: {integrity: sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==} + '@smithy/node-http-handler@4.10.0': + resolution: {integrity: sha512-nrh7VxqzPQS/ip1hS293aI/OAWDWARQvjUxCfuKhyrfHa2gTdk28066RNeWLI1uuoHXaKAkOF8IcSAHuOp0+SA==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@5.5.16': - resolution: {integrity: sha512-iPaxIe9mTyidyhM6WF2/gSLPezyCwSlZcqlDBj2KCoSS994iw4yf1se1xmZkWsY98ZpwjDR/ZMubOSVx+Nrokw==} + '@smithy/protocol-http@5.6.0': + resolution: {integrity: sha512-8XwMdtETS0/pEGPa9wbWu1XQmFIrgPUx6yjxcsR0oVyXYeY15JRpwz3RJHIhpr0uXgEJDPbQbdF5eLcfagI7pA==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.6.12': - resolution: {integrity: sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==} + '@smithy/signature-v4@5.7.0': + resolution: {integrity: sha512-hCynhm22wMJ8wTF9crcwu8mxggtUrSLLJgDcGUvYFBqpofxycYJCGKOMYg4xtPPFtgNiDJSYmhsWLTrcU/g59Q==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.14.16': - resolution: {integrity: sha512-UzWzH88hntrIn1wMPlmt/6vmQL+FS+pYgqvBsp4QQdgt/DUFZZD0S0xRO7kCfAfz9fqn9boVb+buFWU4LD9PJg==} + '@smithy/smithy-client@4.15.0': + resolution: {integrity: sha512-7ay2gfzkVK6Sj2zqmMhztWM01yK6oSp1UmnaCCcrhqqrqmWeYFI5wecAb4WxGtO3u6letM/JcrgtVgjhutvdpA==} engines: {node: '>=18.0.0'} - '@smithy/types@4.16.1': - resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} + '@smithy/types@4.17.0': + resolution: {integrity: sha512-Aw4joiM0ZdErpo39lCj8phT2lxoiKZV+KZzBxnnQhWVtU2Is/WffQSL04uUWRcXUse9Ln8vXZK6V/FwqRVnQpg==} engines: {node: '>=18.0.0'} '@socket.io/component-emitter@3.1.2': @@ -4414,8 +4660,8 @@ packages: peerDependencies: solid-js: ^1.8.6 - '@speed-highlight/core@1.2.23': - resolution: {integrity: sha512-iRoq6i6JDJP6Mt2A5JaPvzw0pgYHH6k92ij+yXiTrB7T2y9N789aWE3EHWj/5ztlJBokcCBja3iYLVdu5wgnkg==} + '@speed-highlight/core@1.2.24': + resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==} '@sqlite.org/sqlite-wasm@3.48.0-build4': resolution: {integrity: sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ==} @@ -4633,60 +4879,60 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 - '@takumi-rs/core-darwin-arm64@2.5.7': - resolution: {integrity: sha512-co8wfyhQjQfxTtJF+iz+kE57xJUqbjfxrjuks/O1cWdq+fO5ij2dePgi2rN3ObTEanFlMli+lHFe7XpVw4S5+w==} + '@takumi-rs/core-darwin-arm64@2.7.2': + resolution: {integrity: sha512-owOL+JzHJmLxsqvAN6cResnRk9KiiYyGwZ5+6ID7qelrEU2uVSZt5QFdDU+CTyiMxaUpsTs27cuMdv7T4BmzYA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@takumi-rs/core-darwin-x64@2.5.7': - resolution: {integrity: sha512-RXQ78qpuRbNf5Ylny82WMUduZfU7hxX4uAxZc9sr/7vDqFiGNZ/ulFUDZXOeSUPA9gB/DCF21eBNCD6B3AJV/g==} + '@takumi-rs/core-darwin-x64@2.7.2': + resolution: {integrity: sha512-ww9WJOUpYwOcmiB0eiMap//wTIyxrvx5RnlLIezHai7qbBpVQUF2UxlQcT2FjqVoULLsngyIQn/1D/X58h1WgA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@takumi-rs/core-linux-arm64-gnu@2.5.7': - resolution: {integrity: sha512-WLg24vvLskdR7/UjrBEzocWod6aaGzK5bJxNoGF33Sh+IKWxlpSvuvnxJP6g2Rd6Fx2hZcSDzwyPAZ+DWqXVPQ==} + '@takumi-rs/core-linux-arm64-gnu@2.7.2': + resolution: {integrity: sha512-I/mR7vZhrvJBV2Pjfqwqmhuw9UbHTTKhTCK6Yn455johTTI1tYLNWjru7Rq6I/drrxKZCNfM+mbQ4uj3KIvFPQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] libc: [glibc] - '@takumi-rs/core-linux-arm64-musl@2.5.7': - resolution: {integrity: sha512-WzReciz+pJrHzZCQ4uHe0ISgBonf65dZn6eOMsqB2NLhF/+rCyPxtLSdVHjlBwxtxAQnz5bfhr0iKjazjAcSsg==} + '@takumi-rs/core-linux-arm64-musl@2.7.2': + resolution: {integrity: sha512-/fkWyIhfdY/2FRx9iFJzp3GvqaSkxujwl7Ho5IbIFbNzT1rvP0z4mr2Mh/fDxfkF0EEIPWRkonpa22dRhx+Jyg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] libc: [musl] - '@takumi-rs/core-linux-x64-gnu@2.5.7': - resolution: {integrity: sha512-ptgKoAp/VW2E29RWQyOZ6U5+yfRbnOOFn8PldxLsQa9zWyHevU3/MQXmRuucc5g0jg+EeqXJqnhZmSP2fsYvvw==} + '@takumi-rs/core-linux-x64-gnu@2.7.2': + resolution: {integrity: sha512-QfEYZTPbT6qKbcBFU9mWs6ofo5WaEvwnXeKZ9/w3LLvoHsb1oUlTlIdG57uiRvpxvlyIcYYU/rGeZni1qvqXbg==} engines: {node: '>=18'} cpu: [x64] os: [linux] libc: [glibc] - '@takumi-rs/core-linux-x64-musl@2.5.7': - resolution: {integrity: sha512-If7uMmVgFz9rjNkbryMES0EydED3p9FJm4CVt9rwkXNeSIiGK4ak6Wvn6mB8dP5L2fEwGWJkWrNqk2rFuJ+9/Q==} + '@takumi-rs/core-linux-x64-musl@2.7.2': + resolution: {integrity: sha512-VtiLCGMYuKVcAnTH9VySpdLGZ5GKsUyrr0GGbpIOWNrDF/pUQmMf5cb0/Vp4nQcYYiI/5f/aza0RdEfKd2lkMA==} engines: {node: '>=18'} cpu: [x64] os: [linux] libc: [musl] - '@takumi-rs/core-win32-arm64-msvc@2.5.7': - resolution: {integrity: sha512-mJh0ogn3dVeQ2y3PgNxpVHYtyc27zHn/Cyo8K4ak96K6liM7cx+YZuNUgxl878s9fOaaQXOCveHBrHp7g6znXQ==} + '@takumi-rs/core-win32-arm64-msvc@2.7.2': + resolution: {integrity: sha512-wbENZJ6axTloIpb2G4oStfALEKCJhiN8KENjHwBSLHd4y2wkYZdYZpdT0j6uDumCy3PBJyEqh+orxOwkaJWyaw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@takumi-rs/core-win32-x64-msvc@2.5.7': - resolution: {integrity: sha512-BOxfKDDQouJA/qyKwLXPA8czpFpiVJ6G798AMjInfI5FejMGnE+0NnOX+Ju8FEeWEQkezIiEJZrDwwKEBCFDJg==} + '@takumi-rs/core-win32-x64-msvc@2.7.2': + resolution: {integrity: sha512-4pJNo1U29MLay8smF5U8Eiu4uT/Uc0pGNmvuRH7SSYSsjbmLNMSgE2quhdMrWHsyIy3pkanJDEXQIoA5kFa/Cg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@takumi-rs/core@2.5.7': - resolution: {integrity: sha512-Il/sFYD35Eus54JgpTuVpJVpsOxP+6uZUzqzECiXcOzZ/1eW0qI4+zKgl2P4Ir+ZBr8Pu5+0oDjCryXNr/XBxg==} + '@takumi-rs/core@2.7.2': + resolution: {integrity: sha512-9VB4SDAmpsYJf30un5xs0HVqEsEdE9CKBrpsGBlsWUSzUBHIlCz8gPbF7Uo4jvasRt9XVJgywcOLJJRBjfVL7w==} engines: {node: '>=18'} peerDependencies: csstype: '*' @@ -4694,8 +4940,8 @@ packages: csstype: optional: true - '@takumi-rs/helpers@2.5.7': - resolution: {integrity: sha512-9kSRKd/3h5NLbkhP597VRPsLJPCcpdLKNsyQuO1Z/EE+ulNN0EbeVEV4nYTKSpqXVqxsfe7zZB9i/v7yZcgspw==} + '@takumi-rs/helpers@2.7.2': + resolution: {integrity: sha512-tyLpwsqvuAMNkDGGXQcy2g6np0sVb42C0rnGrvR0Sicg1i5Wf9YCd9TA5eUq7ruQmNyT9YxsFMiikZaLxlbdJg==} engines: {node: '>=18'} peerDependencies: preact: ^10.0.0 @@ -4706,8 +4952,8 @@ packages: react: optional: true - '@takumi-rs/wasm@2.5.7': - resolution: {integrity: sha512-tM5O+m4DaNvhBskqmzsUJcPmxuWiIgatE6GFR7GEMxoRdd8CdCUd1ETKX9Swg+Pwaxyg5u9i31Hy3pbWxScS1g==} + '@takumi-rs/wasm@2.7.2': + resolution: {integrity: sha512-EErw+S4GvMsD8J/GR2TvBvG5vlScufl8T9/WOHW5Y+FCrRvo2wt+zEKw8oCqlF+qvFhKAdaFjD8KQ3B9OKgVBw==} engines: {node: '>=18'} peerDependencies: csstype: '*' @@ -4720,8 +4966,13 @@ packages: peerDependencies: typescript: '>=4.7' - '@tanstack/db@0.6.17': - resolution: {integrity: sha512-/i6+dedEkOCVQbTQtCjQHx3Nqlkqe6zvi+9/JPnSgm47o+akoaPqcSwxTmesDZxc/efHokUtzeD2ocow257RdQ==} + '@tanstack/db@0.7.0': + resolution: {integrity: sha512-ZQns5TIWb0m6PEIf4c2vU7NyFq+QgMuL7zIo70243NSEbLPtd7hd86t3ksm/H3ozwcuCb7vL14BHt/OxNyPQGA==} + peerDependencies: + typescript: '>=4.7' + + '@tanstack/db@0.7.2': + resolution: {integrity: sha512-gQjUIMKRK1ktg+dXFsTNK2pGgasGOz/qV877nG6vOfC5qggu6yDZzFn9PVhKp5t8um/4kFc4cfPlJP2WM6RmjA==} peerDependencies: typescript: '>=4.7' @@ -4779,16 +5030,16 @@ packages: peerDependencies: solid-js: '>=1.9.7' - '@tanstack/form-core@1.33.3': - resolution: {integrity: sha512-htLxe/50GpUxbi2arJleh6uQkw72UOy+3Q0d1AadO3lfBTjs1e51GzyrKk/w8I7qXSkaSnF/JbYlyE+cwbJGNw==} + '@tanstack/form-core@1.33.5': + resolution: {integrity: sha512-3dfx9MBP0aq5sXKteikG629X9oviptrQj0IFRk9YGcb+lB7Kv5x8S17oOSk1wUWgjQZ4xVJEMbKwOAODymocgA==} - '@tanstack/form-devtools@0.2.32': - resolution: {integrity: sha512-eJX7L7KH0nAYEExg6sWcwYT8fv0vrFUivmw0PKNnDU1hw6juLUjvur4vcaNrLLt/0edimnuIHUgfQyVEaH/jKg==} + '@tanstack/form-devtools@0.2.34': + resolution: {integrity: sha512-AqKQDO9lr8dCYh4cp6bYNhqlut8dH6bQFFXWEhX1+0EI4lsom1scO+L6yapQQAy/K/KS3EvjXRLrGu2cHFMTPg==} peerDependencies: solid-js: '>=1.9.9' - '@tanstack/history@1.162.0': - resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==} + '@tanstack/history@1.162.1': + resolution: {integrity: sha512-DR9t6lfLVdrjgCwpglrR9DR7Ok8/HlXjcOE+goWXF3zyuLUO/ug7vMbSFxTqrQTtbRghJfyhmIZ0S6LhPIy44w==} engines: {node: '>=20.19'} '@tanstack/pacer-lite@0.1.1': @@ -4806,8 +5057,14 @@ packages: '@tanstack/query-core@5.101.4': resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} - '@tanstack/query-db-collection@1.2.1': - resolution: {integrity: sha512-2IwtxdolgPMwLoV7TKaB+1qVGU7ukulacCQqhCt1/50+x/r9sLXF2668fy6OnLd0mEkkEthzCa+J3ZbiA4kNbg==} + '@tanstack/query-db-collection@1.2.2': + resolution: {integrity: sha512-ud9JltwBtICMgvB3JCj5cwA0RiSiwJTUCaiu9/td+sSwzxyNceXQh98uAK/Kc0KfZ9+l9Nxyvay8TsB4xLj7Ug==} + peerDependencies: + '@tanstack/query-core': ^5.0.0 + typescript: '>=4.7' + + '@tanstack/query-db-collection@1.2.4': + resolution: {integrity: sha512-5VpOxTC4tfaxpgcCaXEtKfRUwC1xJD1FYou6kSsjqb5gqEGHz4HKOk5sv1H7HaT2B9m3MkXvrcTeIlO4EU6PJQ==} peerDependencies: '@tanstack/query-core': ^5.0.0 typescript: '>=4.7' @@ -4815,8 +5072,8 @@ packages: '@tanstack/query-devtools@5.101.4': resolution: {integrity: sha512-z5IPHnDX3aUWeTWlRKLyooBQekaCAw4xRpZqPQ390RiWTDBcTynjpPT221BArw0u2+pnQMdGvPQI9YNNubBcmA==} - '@tanstack/react-db@0.1.95': - resolution: {integrity: sha512-Om2qgKtoK+iTcE3nR+MaPQsM4JUsDWB8LqFt4yFjwuSawwMJ7bcPnc7gP0V06YKOtm5MzrIu8IV/OexMJbraKQ==} + '@tanstack/react-db@0.1.96': + resolution: {integrity: sha512-NIjRiFH9KqNnWRtixFal22KA+9b2ktBxI7TbW7xIVa2/sKOHSxFcPAkz6HEC3V7O9PfDTh3j8aezj02CxjHrLw==} peerDependencies: react: '>=16.8.0' @@ -4829,13 +5086,13 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-form-devtools@0.2.32': - resolution: {integrity: sha512-QpoNvwFIlJPzTrwIV/Lw6/yr+8IqGUEqAAaBwhlZwGKWgm9YYsxcJoFxZXReL84+BM0rQbkJ2EDNzjkqb4SJ3A==} + '@tanstack/react-form-devtools@0.2.34': + resolution: {integrity: sha512-Sg+6nuTNqXnODPtUJDn/WZmLznNxilIK3hFqeXzKVnzfdzUKczJr5PnXXh3J6SZj+QjveJdvwC2M6Rm2oIypoA==} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-form@1.33.3': - resolution: {integrity: sha512-lkzI/y15fHC8lKvzsLXFLLqGWroa+okvV2cKRCGAL+d0Kdf040fdMZbKh6uCXDMc08Ngpl8G3VZFnZ5KVUkUIw==} + '@tanstack/react-form@1.33.5': + resolution: {integrity: sha512-LlRB28qJwO/QCGaHvWnbdh4haBgTFiZVmzA2uzxSBS3YA7/IqrQ6HOBK70CkFQ+DbflZ7NawsmSln13h5iIdTA==} peerDependencies: '@tanstack/react-start': '*' react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -4873,15 +5130,15 @@ packages: '@tanstack/router-core': optional: true - '@tanstack/react-router@1.170.19': - resolution: {integrity: sha512-cK7VUsc9+8gYZMqwJjCAhrmL9wPhkwxEcZ7cxlHdBjjmqHbZyNxXAml/KfLmSuxRDQGVk2iofpoHclDTSrkmmg==} + '@tanstack/react-router@1.170.27': + resolution: {integrity: sha512-Hxl49xzd8ffWd2ZMigqfXZmpySpixWGvjq5zfh2nK2DbzzDH6IGVh+iUuwarK9MJNcnhWPZ85tOoy6o/OmNlww==} engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-store@0.11.0': - resolution: {integrity: sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w==} + '@tanstack/react-store@0.11.1': + resolution: {integrity: sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -4904,8 +5161,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.171.16': - resolution: {integrity: sha512-/qwawP5a45puA9DQtG8syrChWCV7uGMYs1p5rwgrICgiS/0pHo7zNOFyfVI02J1azWhCfFc6w6+SuiivPESYAA==} + '@tanstack/router-core@1.171.22': + resolution: {integrity: sha512-sitsuRkz4qpTjIAV97S5zFCoeGv0OFd6+VWg3ZcIlQbi5R4NIXfz6ogg51n5w6rvTwSODz/lKWb5dl5tvh2bQw==} engines: {node: '>=20.19'} '@tanstack/router-devtools-core@1.168.1': @@ -4918,16 +5175,16 @@ packages: csstype: optional: true - '@tanstack/router-generator@1.167.22': - resolution: {integrity: sha512-tiKrH6xuCdTfmm4UuHSpLsZ5q2pwmhNbGpExp5u/CCRObjQM5nxz8Dce/X3w86ST6VSZFQMBNjLcsUMnVlJw/Q==} + '@tanstack/router-generator@1.167.28': + resolution: {integrity: sha512-AxvzdqQoBxrA8hoO1fHYg4cAUVug/xLqQhsGbNG1PelU3RbYRYEtDim7+HbYQCOqJaEuvV8tCvrTPTnT69iIwg==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.168.24': - resolution: {integrity: sha512-FfPW45gfZHDC25AHo4MJB3ZljXEKFJenEgIDTELwnI6FtS90vXTQXjOjdTVNs/7B4R8+wLf7nWVrPSvBMp7zWw==} + '@tanstack/router-plugin@1.168.30': + resolution: {integrity: sha512-Z53FeZjSddyn3c+lmUGHOU3bhZPpaFabcynja8/s87CZeyMYS7oNgUMoaYZAhVLk9cAEC/z3fkqISXqktZadDA==} engines: {node: '>=20.19'} peerDependencies: '@rsbuild/core': '>=1.0.2 || ^2.0.0' - '@tanstack/react-router': ^1.170.19 + '@tanstack/react-router': ^1.170.26 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' vite-plugin-solid: ^2.11.10 || ^3.0.0-0 webpack: '>=5.92.0' @@ -4947,8 +5204,8 @@ packages: resolution: {integrity: sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ==} engines: {node: '>=20.19'} - '@tanstack/store@0.11.0': - resolution: {integrity: sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw==} + '@tanstack/store@0.11.1': + resolution: {integrity: sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==} '@tanstack/store@0.8.1': resolution: {integrity: sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw==} @@ -4966,33 +5223,33 @@ packages: '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} - '@turbo/darwin-64@2.10.8': - resolution: {integrity: sha512-po+7rfJfUnFXjWlcoN2RwhErgzCdRtBc1T26vYPcywHlggmCQiQe1uWaE4j+BibI2uY9/2pDoFzMN0rmSaPFOw==} + '@turbo/darwin-64@2.10.9': + resolution: {integrity: sha512-Jh+pTGXLNz8+1tkUU13TI/f+ZOI+OvC4YbHi1H+57iSpLt5DR3xgptd+4sA07RdjdRR/RX/01uQQu2OkbzIefA==} cpu: [x64] os: [darwin] - '@turbo/darwin-arm64@2.10.8': - resolution: {integrity: sha512-+zB2btDJ00lnPRuqOvpVvgl4x34k/djZQGZTTCfjn7JgNCl8QFY5Njo5+dqkY1g/+9gbbsnAvWm9CmJg9ebcXA==} + '@turbo/darwin-arm64@2.10.9': + resolution: {integrity: sha512-aqtpPkiIC4IUas8Vv27oJ3aDfTuP1d5wofd01dZ7gfhHRrIKuFdqQb4imvNnVFahcOViF9Jh8Oi7feDoz8/ciA==} cpu: [arm64] os: [darwin] - '@turbo/linux-64@2.10.8': - resolution: {integrity: sha512-K1dxqiVisyN7cViVsfQLs6xscQbYuI8aO2nbUhFURDACgEDfZRdP/b4CCxeosBJpcMfhYyiibWqJorCnvz9kKg==} + '@turbo/linux-64@2.10.9': + resolution: {integrity: sha512-XyAneUBsS5uNOUOjBSs81zyigMVwwhVUd3u7F2JFMKGQk6F7eNAiOEBASJ+aHLldjYsOEjhfW+5gWIqwziyFFw==} cpu: [x64] os: [android, linux] - '@turbo/linux-arm64@2.10.8': - resolution: {integrity: sha512-Gi77ibVnrE1fEmvr+/wBD/yvRqhwp/RQuCp2+//lv1U1wNFFyVg0V7Wj8FG9FXPFAw5QHReo8rxc9+wBSDZjzA==} + '@turbo/linux-arm64@2.10.9': + resolution: {integrity: sha512-5jAcldLnkuWIjujGhCn2MGIeUwW8IVNOq9Sce4EEzzgLcxmhTasbV0RiW6ZkaRdOjmOCGzeNXPLkDJEOIucOLw==} cpu: [arm64] os: [android, linux] - '@turbo/windows-64@2.10.8': - resolution: {integrity: sha512-znnLO1haJPYTHoKMKwlAvlkjRiYbbhBzME6wIGaMd+fwir23U6jVd1ecaTWWi1fbnRVqxMfgDBKseQ/hLKb83g==} + '@turbo/windows-64@2.10.9': + resolution: {integrity: sha512-u1xGpGlefzuhBedbt/VR2nWdftfFVZwPeDg9e5uZl68sV1fL3HNYTNr5VyHkzgtPK2VTYg1x4OKZuGrh1Gvhtg==} cpu: [x64] os: [win32] - '@turbo/windows-arm64@2.10.8': - resolution: {integrity: sha512-VN30vh3b3Czh2WzYHNTfF1FE0YMZ5aHsLO8dBMGHJewA6792wX6iJR8ZxlzFW6WdOu0gEAKIvlYhfyT81Wkm4Q==} + '@turbo/windows-arm64@2.10.9': + resolution: {integrity: sha512-W2Ub165Qv0iMFljbYKxxbZN+2dVSngnzEjQNEFXtnz5oJVx9XSypmNmUvMtuYMeZ3kdVC1zI7yd/rtuX+SDnbg==} cpu: [arm64] os: [win32] @@ -5047,14 +5304,14 @@ packages: '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} - '@types/node@26.1.2': - resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} + '@types/node@26.2.0': + resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} '@types/nodemailer@7.0.12': resolution: {integrity: sha512-80vKwiIsVSyFA1rRovH59jNPLBOuc6dRZIHEu40gXTkBkZnQv8vog1xSGEb9j5q/tdMAs5ivvDR2pLTU0hGHXA==} - '@types/pg@8.20.4': - resolution: {integrity: sha512-Jz7UDOlIiFJuacC0TlBoLyNtmwlA/wpIyPDd3tvUqlRM+HzkWy2xUgpFpaXtbfTAFF6sIGq5lsCDBdJnhky1Xg==} + '@types/pg@8.21.0': + resolution: {integrity: sha512-AYdtudzabjLZgVgRZmAnU8bAnVUXzuJX2IYHeSIiIHm68olD+LgQYCGWdtcNYnP0uq9c4S4NibVG3Ni7VbKW7Q==} '@types/prismjs@1.26.6': resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} @@ -5338,8 +5595,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -5386,8 +5643,8 @@ packages: astro-seo@1.1.0: resolution: {integrity: sha512-G6LDNDyga30o+52v58jU7C35n3cORUzk7RSuY+xf/ZRbW6JiNplyaOO1VoYVKO+xzYNaBcxqNln2RFRR/wgJNQ==} - astro@7.1.6: - resolution: {integrity: sha512-83x9rYbHazMaZkYrAFRVZXSQx2moFkz0F7cjTDUF3GWfS0a3p2vZXG1ZdhV86rStHApQCodBJW+XTD37xISIrQ==} + astro@7.2.1: + resolution: {integrity: sha512-ynyMTiyF//GLcd8+gRNbvcKgS1ht+qTgp/fkkHIpGYNILLseQqQRuPZ9PqXYKfsact4J1emZaXRDpg2bl35Pag==} engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true peerDependencies: @@ -5406,8 +5663,8 @@ packages: aws4fetch@1.0.20: resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} - axe-core@4.12.1: - resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + axe-core@4.13.0: + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} engines: {node: '>=4'} axobject-query@4.1.0: @@ -5445,8 +5702,13 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.11.12: - resolution: {integrity: sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==} + baseline-browser-mapping@2.11.13: + resolution: {integrity: sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + baseline-browser-mapping@2.11.14: + resolution: {integrity: sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==} engines: {node: '>=6.0.0'} hasBin: true @@ -5557,8 +5819,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.7: - resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5594,8 +5856,8 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001806: - resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -5788,8 +6050,8 @@ packages: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} - core-js@3.49.0: - resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + core-js@3.50.0: + resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -5878,8 +6140,8 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - decode-uri-component@0.4.1: - resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==} + decode-uri-component@0.5.0: + resolution: {integrity: sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==} engines: {node: '>=14.16'} dedent@1.5.1: @@ -6238,6 +6500,155 @@ packages: zod: optional: true + drizzle-orm@1.0.0-rc.5-169397b: + resolution: {integrity: sha512-xf5TudQhFplMMzjwgmMY5f64Sqbv4iwXxdTHBHqvLMt+SHyY8rAYhzNLQchEkDxiqMclTS/txPEOg5pdQy0Tcw==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@drizzle-team/minipg': '>=0.3.2' + '@effect/sql-d1': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-libsql': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-mysql2': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-pg': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-pglite': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-sqlite-bun': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-sqlite-do': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-sqlite-node': '>=4.0.0-beta.105 || >=4.0.0' + '@effect/sql-sqlite-wasm': '>=4.0.0-beta.105 || >=4.0.0' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=17' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@sinclair/typebox': '>=0.34.8' + '@sqlitecloud/drivers': '>=1.0.653' + '@tidbcloud/serverless': '*' + '@tursodatabase/database': '>=0.7.1' + '@tursodatabase/database-common': '>=0.7.1' + '@tursodatabase/database-wasm': '>=0.7.1' + '@tursodatabase/serverless': '>=1.4.0' + '@tursodatabase/sync': '>=0.7.1' + '@types/better-sqlite3': '*' + '@types/mssql': ^9.1.4 + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + arktype: '>=2.0.0' + better-sqlite3: '>=9.3.0' + bun-types: '*' + effect: '>=4.0.0-beta.105 || >=4.0.0' + expo-sqlite: '>=14.0.0' + mssql: ^11.0.1 + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + sql.js: '>=1' + sqlite3: '>=5' + typebox: '>=1.2.0' + valibot: '>=1.0.0-beta.7' + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@drizzle-team/minipg': + optional: true + '@effect/sql-d1': + optional: true + '@effect/sql-libsql': + optional: true + '@effect/sql-mysql2': + optional: true + '@effect/sql-pg': + optional: true + '@effect/sql-pglite': + optional: true + '@effect/sql-sqlite-bun': + optional: true + '@effect/sql-sqlite-do': + optional: true + '@effect/sql-sqlite-node': + optional: true + '@effect/sql-sqlite-wasm': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@sinclair/typebox': + optional: true + '@sqlitecloud/drivers': + optional: true + '@tidbcloud/serverless': + optional: true + '@tursodatabase/database': + optional: true + '@tursodatabase/database-common': + optional: true + '@tursodatabase/database-wasm': + optional: true + '@tursodatabase/serverless': + optional: true + '@tursodatabase/sync': + optional: true + '@types/better-sqlite3': + optional: true + '@types/mssql': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + arktype: + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + effect: + optional: true + expo-sqlite: + optional: true + mssql: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + drizzle-seed@1.0.0-rc.4: resolution: {integrity: sha512-WztS8tuuWV9Wqx5rMIhFlFuIfE0DPf7Zher3WtCumhSRgsAo5dASRmzS/w+6h22hEfOF/YJk6f3vKIqltKhUEg==} peerDependencies: @@ -6257,11 +6668,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@4.0.0-beta.103: - resolution: {integrity: sha512-pE8TxF4m2tQzVI+77dIlm3s+81TACV1AiX1JEkvY+zVuxgQQ8aGSkqXNJF6b/ST+coCSk5cUdbULpQ7sm4oHyw==} + effect@4.0.0-beta.107: + resolution: {integrity: sha512-OoBAv8eF+yanc+C6xhgEUnWeXUSHA6ynnscYqpkAY9GSnzZWystsIjBowVqCkLpHGlnRtdIqYT3wHwpOY6JDnQ==} - electron-to-chromium@1.5.401: - resolution: {integrity: sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==} + electron-to-chromium@1.5.405: + resolution: {integrity: sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==} elkjs@0.11.1: resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} @@ -6369,6 +6780,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -6406,9 +6822,6 @@ packages: estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -6419,8 +6832,8 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - eventsource-parser@3.1.0: - resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} engines: {node: '>=18.0.0'} eventsource@3.0.7: @@ -6529,9 +6942,6 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} - find-my-way-ts@0.1.6: - resolution: {integrity: sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==} - find-process@2.1.1: resolution: {integrity: sha512-SrQDx3QhlmHM90iqn9rdjCQcw/T+WlpOkHFsjoRgB+zTpDfltNA1VSNYeYELwhUTJy12UFxqjWhmhOrJc+o4sA==} hasBin: true @@ -6641,8 +7051,8 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-tsconfig@4.14.1: - resolution: {integrity: sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==} + get-tsconfig@4.14.2: + resolution: {integrity: sha512-XpwZALwwl/BaKTAyC6+c5T8y6kCg2jk+XGqOVrKIQmW49pNypYLMRjCUXqa28tQgJlhS2RlzP7sc+Rx7W6qsfw==} get-tsconfig@5.0.0-beta.4: resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} @@ -6696,8 +7106,8 @@ packages: h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} - happy-dom@20.11.1: - resolution: {integrity: sha512-XSt8tMzbW9ymE7687xztkO1ckR7qJNQ3LywY9vlYGhGi3zXrGBHuUo2Cl1ztZaICW+1eAGdkLbj6iwVqDT33kg==} + happy-dom@20.11.2: + resolution: {integrity: sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw==} engines: {node: '>=20.0.0'} has-flag@4.0.0: @@ -6792,8 +7202,8 @@ packages: history@5.3.0: resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} - hono@4.13.0: - resolution: {integrity: sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==} + hono@4.13.1: + resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==} engines: {node: '>=16.9.0'} html-entities@2.3.3: @@ -6875,10 +7285,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@7.0.0: - resolution: {integrity: sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==} - engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -6886,8 +7292,8 @@ packages: resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} engines: {node: '>=12.22.0'} - ip-address@10.4.0: - resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==} + ip-address@10.5.0: + resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -7134,8 +7540,8 @@ packages: resolution: {integrity: sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q==} engines: {node: '>=20.0.0'} - kysely@0.29.4: - resolution: {integrity: sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA==} + kysely@0.29.5: + resolution: {integrity: sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ==} engines: {node: '>=22.0.0'} leac@0.6.0: @@ -7344,16 +7750,16 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@1.28.0: - resolution: {integrity: sha512-fARAFJULsGuDDydjp6+6blekG/sBIM29TerzLjc9bQUKAcEfrSc4ZQKb25KRz4OMKd87cZTb5dgq0w/T6KufVg==} + lucide-react@1.31.0: + resolution: {integrity: sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magic-string@1.1.0: - resolution: {integrity: sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==} + magic-string@1.1.1: + resolution: {integrity: sha512-qFemKPzc3ttrYVaMmnSkGtGc5nE6Ncl4bj7c9IE6C9OUIRXjf6PzJ+UZ1xhVIjYc7dolHq3qKzpAJPZUbvNj+A==} magicast@0.5.4: resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} @@ -7598,8 +8004,8 @@ packages: engines: {node: '>=22.0.0'} hasBin: true - miniflare@5.20260730.0-alpha: - resolution: {integrity: sha512-8/dspSXDshP6nSkCpjKO7BYc2qZoYSXm7iM+QxY7qJyJpAB3onnQSaiu0cvKJlfuMGwULl55hG69FJCcCMXU1Q==} + miniflare@5.20260811.0-alpha: + resolution: {integrity: sha512-sypXsD5fjY88fZNedPqnwrwR1dwfnfbfW7MfvMyIfPJdtRiCCOpUnjWGeFVYYZ+0fQVICye6Juu+vZgzTEx8XA==} engines: {node: '>=22.0.0'} minimatch@10.2.6: @@ -7647,15 +8053,12 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - multipasta@0.2.8: - resolution: {integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==} - mute-stream@3.0.0: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.17: - resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -7751,8 +8154,8 @@ packages: node-mock-http@1.0.5: resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} - node-releases@2.0.52: - resolution: {integrity: sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==} + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} nodemailer@8.0.11: @@ -7968,8 +8371,8 @@ packages: pg-connection-string@2.14.0: resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} - pg-cursor@2.21.0: - resolution: {integrity: sha512-IYvk/j+Suhtbo/C3uOf4JLsLK/gWxOTUOmYbDsbKnLaVJDq+KwhwK6ngpRfiCk8eDMS3AmGQABZCv0cREEzHQw==} + pg-cursor@2.22.0: + resolution: {integrity: sha512-knzXLKqarTjOvb3qDSW0JiGsazmxwEKXrqHfWRte7XUsOYccQRafn3BLnQobWwInkzFJSyOej8y8cQRh2z3kGw==} peerDependencies: pg: ^8 @@ -7986,8 +8389,8 @@ packages: peerDependencies: pg: '>=8.0' - pg-protocol@1.15.0: - resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==} + pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -7997,8 +8400,8 @@ packages: resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==} engines: {node: '>=10'} - pg@8.22.0: - resolution: {integrity: sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==} + pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -8053,16 +8456,16 @@ packages: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} - postcss-selector-parser@7.1.4: - resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + postcss-selector-parser@7.1.5: + resolution: {integrity: sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==} engines: {node: '>=4'} postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -8104,8 +8507,8 @@ packages: resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==} engines: {node: '>=12'} - posthog-js@1.411.0: - resolution: {integrity: sha512-nTYMucbJHotQXHWpHH6x3UHP9IrnHqoxrIwfJeQ7yHA3rcNGF/SEa4Z0UlfssMtcXHOH5bse+8ASouc0YL+02w==} + posthog-js@1.416.0: + resolution: {integrity: sha512-9Rd0tq2WTA1U07VucWuCMwlzr+YvODglyN4mOf61TVrxPeMA7KVBiOEAThqaIvAv2kHbZvwTjbd8Ww2oTs221w==} powershell-utils@0.1.0: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} @@ -8195,8 +8598,8 @@ packages: prosemirror-changeset@2.4.1: resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} - prosemirror-commands@1.7.1: - resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + prosemirror-commands@1.7.2: + resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} prosemirror-drop-indicator@0.1.4: resolution: {integrity: sha512-YaRB1pZmU5GCorPVWbc9dbhbwqr4iMBO/AjPu4BTKHCUzxEDUXje2dUyoxOHib/z4uyPUZTJz64h7mHDJZeSzA==} @@ -8318,8 +8721,8 @@ packages: query-selector-shadow-dom@1.0.1: resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} - query-string@9.4.1: - resolution: {integrity: sha512-lSyJeN3RuaG7DZGWThtYRhk96+kEyZ/+doZpERuWbjeFL+Ok3vEat/swU498rAI0NcVt5/RJp8UDuLz7FckxrA==} + query-string@9.5.0: + resolution: {integrity: sha512-YlJmwNyi0RGYjlxYcuDncMsxFU7YyutbuI7gTm8ySxIGBlwx5yiBCOD5ig9ZNoHkawk/1Dey0N5mEfcUybMVAA==} engines: {node: '>=18'} queue-microtask@1.2.3: @@ -8357,8 +8760,8 @@ packages: peerDependencies: react: ^19.2.8 - react-email@6.9.1: - resolution: {integrity: sha512-uUDRgFukMUXRlrsCNGlA0PZuUlQ44faI9hT/D7uMjozdumLBdHjfQttQswRYLjyLW8fFtg2HBrxAESbFU4ZKKA==} + react-email@6.9.2: + resolution: {integrity: sha512-A6SiRdH1U7qJcfgpaJ1BYGud8GbnPwDw61l3PP3JR1qMAmBgTlL02UuCPwPOClwc1x+saUU9OxJfPQHMNaTBMA==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: @@ -8398,12 +8801,12 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + readdirp@5.1.1: + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} - recast@0.23.19: - resolution: {integrity: sha512-T98lym7kH+pnZmRaD8yDRdaNqyUbwnbEBx0MuchrzMFOEMray4AO3ZJoTUZ5r78Ao78X/OhzW0DL8GB85w/I2w==} + recast@0.23.21: + resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==} engines: {node: '>= 4'} recma-build-jsx@1.0.0: @@ -8520,8 +8923,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.2.2: - resolution: {integrity: sha512-opwpo1tQBAcpSUJDt94B7hhLNGOKjCdE//XXjeLrnx9b83bjnw45tXdg1b09yEw/VLFBJGZpwRULMmOZo7ol+A==} + rolldown@1.2.4: + resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -8661,8 +9064,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@4.4.2: - resolution: {integrity: sha512-P8F/dFhRevaw2uSdeIYlq/5SXZNY85DPtmXQ947gD1Zj2JqO5AkNvVVBar0Me9JkFx3uzVud/qOtP5ek9NEQGA==} + shiki@4.4.3: + resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} engines: {node: '>=20'} side-channel-list@1.0.1: @@ -8706,8 +9109,8 @@ packages: resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} engines: {node: '>=8.0.0'} - smol-toml@1.7.1: - resolution: {integrity: sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==} + smol-toml@1.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} engines: {node: '>= 18'} socket.io-adapter@2.5.8: @@ -8890,8 +9293,8 @@ packages: tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - takumi-js@2.5.7: - resolution: {integrity: sha512-8fjdCaEuRH7Lw4M2bZ94KsURZuM6nuux0IziNBFMdWgc5NxGvPuM59l6lkqKsFz/EmcVixaXkd81itjh/CQC9Q==} + takumi-js@2.7.2: + resolution: {integrity: sha512-bFGu6BrdSo4OoLKwfb6fxmwB7NqmOFmK7oa6sHl6giqcc1qeqbwrYIT5zkUel83/OeY0juyR9E/IbJwugVgI4g==} engines: {node: '>=18'} tapable@2.3.3: @@ -8941,10 +9344,6 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - toml@4.3.0: - resolution: {integrity: sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A==} - engines: {node: '>=20'} - totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -8987,13 +9386,13 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} - tsx@4.23.6: - resolution: {integrity: sha512-D/YYGUDqKlLvXhM5fBBbiENaGICxLfU4viHnZEkgmgplnDFa+Kczy34VV7AmLJgdzisv0I/J3zitfC26JH3GXg==} + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} engines: {node: '>=18.0.0'} hasBin: true - turbo@2.10.8: - resolution: {integrity: sha512-9+8YX5QOkGXzZxcIykTHgaooRHGMWO+jfdyRK0o+rN0U7hBIig2MrJ8r/aNzIPDPhdA73SGb0O+tIztaModTMg==} + turbo@2.10.9: + resolution: {integrity: sha512-Yl9+ukxH+UmPtKidpDkjn82tvPoEvFNb9UACd9vUomN1Ft0cwl3rx0P8yC1D93W9EOsWRMjllvIDG8y25sFOog==} hasBin: true tw-animate-css@1.4.0: @@ -9079,8 +9478,8 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unifont@0.7.4: - resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + unifont@0.7.5: + resolution: {integrity: sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==} unist-util-find-after@5.0.0: resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} @@ -9216,8 +9615,8 @@ packages: until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -9317,8 +9716,8 @@ packages: yaml: optional: true - vite@8.2.0: - resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} + vite@8.2.1: + resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -9575,12 +9974,17 @@ packages: engines: {node: '>=16'} hasBin: true - wrangler@4.118.0: - resolution: {integrity: sha512-9pkBw/b8zWqGx2S+oLhgHMR1M/4VOE8SynUFABnGWiSFGlcOQ4xiI/B71Xf66RYP2xzngU37IQFPtUruij3lYw==} + workerd@1.20260811.1: + resolution: {integrity: sha512-kh+FFm55JQ4ssxhHZV9VPdMQq3D1nHxNJgwxMtWGD4dGppJvLySdguTRDKgeNTvgq6heSz+6TTXyPSDGj8Yllw==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.122.0: + resolution: {integrity: sha512-qkskzgQ76Y1qvVe5JARgvc3RISq6BC2rPoxQhFoKH1dKIwQc3GDFttQ/7m2OfeQ+tmQRzynv2dy/DXnxFCj2Lw==} engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^5.20260730.1 + '@cloudflare/workers-types': ^5.20260811.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -9612,8 +10016,8 @@ packages: utf-8-validate: optional: true - ws@8.21.2: - resolution: {integrity: sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==} + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -9737,7 +10141,7 @@ snapshots: alien-signals: 3.2.1 server-dom-shim: 1.1.0 - '@aria-ui/elements@0.1.12': + '@aria-ui/elements@0.1.13': dependencies: '@aria-ui/core': 0.2.1 '@aria-ui/utils': 0.1.7 @@ -9752,7 +10156,7 @@ snapshots: '@astrojs/check@0.9.10(prettier@3.9.6)(typescript@6.0.3)': dependencies: - '@astrojs/language-server': 2.16.13(prettier@3.9.6)(typescript@6.0.3) + '@astrojs/language-server': 2.16.14(prettier@3.9.6)(typescript@6.0.3) chokidar: 4.0.3 kleur: 4.1.5 typescript: 6.0.3 @@ -9761,15 +10165,15 @@ snapshots: - prettier - prettier-plugin-astro - '@astrojs/cloudflare@14.1.7(@types/node@26.1.2)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0)': + '@astrojs/cloudflare@14.2.1(@types/node@26.2.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0)': dependencies: '@astrojs/internal-helpers': 0.10.2 - '@astrojs/underscore-redirects': 1.0.3 - '@cloudflare/vite-plugin': 1.50.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1)) - astro: 7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0) + '@astrojs/underscore-redirects': 1.0.4 + '@cloudflare/vite-plugin': 1.52.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1)) + astro: 7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) piccolore: 0.1.3 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) - wrangler: 4.118.0(@cloudflare/workers-types@4.20260702.1) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + wrangler: 4.122.0(@cloudflare/workers-types@4.20260702.1) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -9806,7 +10210,7 @@ snapshots: '@astrojs/compiler-binding-wasm32-wasi@0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)': dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3) + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -9849,8 +10253,8 @@ snapshots: js-yaml: 4.3.1 picomatch: 4.0.5 retext-smartypants: 6.2.0 - shiki: 4.4.2 - smol-toml: 1.7.1 + shiki: 4.4.3 + smol-toml: 1.8.0 unified: 11.0.5 '@astrojs/internal-helpers@0.10.2': @@ -9860,11 +10264,11 @@ snapshots: js-yaml: 4.3.1 picomatch: 4.0.5 retext-smartypants: 6.2.0 - shiki: 4.4.2 - smol-toml: 1.7.1 + shiki: 4.4.3 + smol-toml: 1.8.0 unified: 11.0.5 - '@astrojs/language-server@2.16.13(prettier@3.9.6)(typescript@6.0.3)': + '@astrojs/language-server@2.16.14(prettier@3.9.6)(typescript@6.0.3)': dependencies: '@astrojs/compiler': 2.13.1 '@astrojs/yaml2ts': 0.2.4 @@ -9897,10 +10301,10 @@ snapshots: hast-util-from-html: 2.0.3 satteri: 0.9.5 - '@astrojs/node@11.0.0(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))': + '@astrojs/node@11.0.0(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@astrojs/internal-helpers': 0.10.0 - astro: 7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0) + astro: 7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) send: 1.2.1 server-destroy: 1.0.1 transitivePeerDependencies: @@ -9910,17 +10314,17 @@ snapshots: dependencies: prismjs: 1.30.0 - '@astrojs/react@6.0.2(@types/node@26.1.2)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.1)(jiti@2.7.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.6)(yaml@2.9.0)': + '@astrojs/react@6.0.2(@types/node@26.2.0)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(esbuild@0.28.2)(jiti@2.7.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.12)(yaml@2.9.0)': dependencies: '@astrojs/internal-helpers': 0.10.2 '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - '@vitejs/plugin-react': 5.2.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitejs/plugin-react': 5.2.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) devalue: 5.9.0 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) ultrahtml: 1.7.0 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -9942,11 +10346,12 @@ snapshots: piccolore: 0.1.3 zod: 4.4.3 - '@astrojs/solid-js@7.0.1(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(solid-js@1.9.14)(tsx@4.23.6)(yaml@2.9.0)': + '@astrojs/solid-js@7.0.2(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(solid-js@1.9.14)(tsx@4.23.12)(yaml@2.9.0)': dependencies: solid-js: 1.9.14 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) - vite-plugin-solid: 2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + vite-plugin-solid: 2.11.14(solid-js@1.9.14)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + vitefu: 1.1.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - '@testing-library/jest-dom' - '@types/node' @@ -9970,209 +10375,209 @@ snapshots: is-docker: 4.0.0 package-manager-detector: 1.8.0 - '@astrojs/underscore-redirects@1.0.3': {} + '@astrojs/underscore-redirects@1.0.4': {} '@astrojs/yaml2ts@0.2.4': dependencies: yaml: 2.9.0 - '@aws-sdk/checksums@3.1000.26': + '@aws-sdk/checksums@3.1000.27': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1103.0': - dependencies: - '@aws-sdk/checksums': 3.1000.26 - '@aws-sdk/core': 3.977.6 - '@aws-sdk/credential-provider-node': 3.972.78 - '@aws-sdk/middleware-sdk-s3': 3.972.72 - '@aws-sdk/signature-v4-multi-region': 3.996.43 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/fetch-http-handler': 5.6.13 - '@smithy/node-http-handler': 4.9.13 - '@smithy/types': 4.16.1 + '@aws-sdk/client-s3@3.1109.0': + dependencies: + '@aws-sdk/checksums': 3.1000.27 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/credential-provider-node': 3.972.79 + '@aws-sdk/middleware-sdk-s3': 3.972.73 + '@aws-sdk/signature-v4-multi-region': 3.996.44 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/fetch-http-handler': 5.7.0 + '@smithy/node-http-handler': 4.10.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/core@3.977.6': + '@aws-sdk/core@3.977.7': dependencies: - '@aws-sdk/types': 3.974.2 - '@aws-sdk/xml-builder': 3.972.37 + '@aws-sdk/types': 3.974.3 + '@aws-sdk/xml-builder': 3.972.38 '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.31.1 - '@smithy/signature-v4': 5.6.12 - '@smithy/types': 4.16.1 + '@smithy/core': 3.32.0 + '@smithy/signature-v4': 5.7.0 + '@smithy/types': 4.17.0 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-cognito-identity@3.972.66': + '@aws-sdk/credential-provider-cognito-identity@3.972.67': dependencies: - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.67': + '@aws-sdk/credential-provider-env@3.972.68': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.69': + '@aws-sdk/credential-provider-http@3.972.70': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/fetch-http-handler': 5.6.13 - '@smithy/node-http-handler': 4.9.13 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/fetch-http-handler': 5.7.0 + '@smithy/node-http-handler': 4.10.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.973.12': - dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/credential-provider-env': 3.972.67 - '@aws-sdk/credential-provider-http': 3.972.69 - '@aws-sdk/credential-provider-login': 3.972.74 - '@aws-sdk/credential-provider-process': 3.972.67 - '@aws-sdk/credential-provider-sso': 3.973.11 - '@aws-sdk/credential-provider-web-identity': 3.972.73 - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/credential-provider-imds': 4.4.16 - '@smithy/types': 4.16.1 + '@aws-sdk/credential-provider-ini@3.973.13': + dependencies: + '@aws-sdk/core': 3.977.7 + '@aws-sdk/credential-provider-env': 3.972.68 + '@aws-sdk/credential-provider-http': 3.972.70 + '@aws-sdk/credential-provider-login': 3.972.75 + '@aws-sdk/credential-provider-process': 3.972.68 + '@aws-sdk/credential-provider-sso': 3.973.12 + '@aws-sdk/credential-provider-web-identity': 3.972.74 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/credential-provider-imds': 4.5.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.74': + '@aws-sdk/credential-provider-login@3.972.75': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.78': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.67 - '@aws-sdk/credential-provider-http': 3.972.69 - '@aws-sdk/credential-provider-ini': 3.973.12 - '@aws-sdk/credential-provider-process': 3.972.67 - '@aws-sdk/credential-provider-sso': 3.973.11 - '@aws-sdk/credential-provider-web-identity': 3.972.73 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/credential-provider-imds': 4.4.16 - '@smithy/types': 4.16.1 + '@aws-sdk/credential-provider-node@3.972.79': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.68 + '@aws-sdk/credential-provider-http': 3.972.70 + '@aws-sdk/credential-provider-ini': 3.973.13 + '@aws-sdk/credential-provider-process': 3.972.68 + '@aws-sdk/credential-provider-sso': 3.973.12 + '@aws-sdk/credential-provider-web-identity': 3.972.74 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/credential-provider-imds': 4.5.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.67': + '@aws-sdk/credential-provider-process@3.972.68': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.973.11': + '@aws-sdk/credential-provider-sso@3.973.12': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/token-providers': 3.1103.0 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/token-providers': 3.1108.0 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.73': + '@aws-sdk/credential-provider-web-identity@3.972.74': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/credential-providers@3.1103.0': - dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/credential-provider-cognito-identity': 3.972.66 - '@aws-sdk/credential-provider-env': 3.972.67 - '@aws-sdk/credential-provider-http': 3.972.69 - '@aws-sdk/credential-provider-ini': 3.973.12 - '@aws-sdk/credential-provider-login': 3.972.74 - '@aws-sdk/credential-provider-node': 3.972.78 - '@aws-sdk/credential-provider-process': 3.972.67 - '@aws-sdk/credential-provider-sso': 3.973.11 - '@aws-sdk/credential-provider-web-identity': 3.972.73 - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/credential-provider-imds': 4.4.16 - '@smithy/types': 4.16.1 + '@aws-sdk/credential-providers@3.1109.0': + dependencies: + '@aws-sdk/core': 3.977.7 + '@aws-sdk/credential-provider-cognito-identity': 3.972.67 + '@aws-sdk/credential-provider-env': 3.972.68 + '@aws-sdk/credential-provider-http': 3.972.70 + '@aws-sdk/credential-provider-ini': 3.973.13 + '@aws-sdk/credential-provider-login': 3.972.75 + '@aws-sdk/credential-provider-node': 3.972.79 + '@aws-sdk/credential-provider-process': 3.972.68 + '@aws-sdk/credential-provider-sso': 3.973.12 + '@aws-sdk/credential-provider-web-identity': 3.972.74 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/credential-provider-imds': 4.5.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.72': + '@aws-sdk/middleware-sdk-s3@3.972.73': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/signature-v4-multi-region': 3.996.43 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/signature-v4-multi-region': 3.996.44 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.41': + '@aws-sdk/nested-clients@3.997.42': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/signature-v4-multi-region': 3.996.43 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/fetch-http-handler': 5.6.13 - '@smithy/node-http-handler': 4.9.13 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/signature-v4-multi-region': 3.996.44 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/fetch-http-handler': 5.7.0 + '@smithy/node-http-handler': 4.10.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/s3-request-presigner@3.1103.0': + '@aws-sdk/s3-request-presigner@3.1109.0': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/signature-v4-multi-region': 3.996.43 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/signature-v4-multi-region': 3.996.44 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.43': + '@aws-sdk/signature-v4-multi-region@3.996.44': dependencies: - '@aws-sdk/types': 3.974.2 - '@smithy/signature-v4': 5.6.12 - '@smithy/types': 4.16.1 + '@aws-sdk/types': 3.974.3 + '@smithy/signature-v4': 5.7.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1103.0': + '@aws-sdk/token-providers@3.1108.0': dependencies: - '@aws-sdk/core': 3.977.6 - '@aws-sdk/nested-clients': 3.997.41 - '@aws-sdk/types': 3.974.2 - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@aws-sdk/core': 3.977.7 + '@aws-sdk/nested-clients': 3.997.42 + '@aws-sdk/types': 3.974.3 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/types@3.974.2': + '@aws-sdk/types@3.974.3': dependencies: - '@smithy/types': 4.16.1 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.37': + '@aws-sdk/xml-builder@3.972.38': dependencies: - '@smithy/types': 4.16.1 + '@smithy/types': 4.17.0 tslib: 2.8.1 '@aws/lambda-invoke-store@0.3.0': {} @@ -10221,7 +10626,7 @@ snapshots: dependencies: '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.7 + browserslist: 4.28.8 lru-cache: 5.1.1 semver: 6.3.1 @@ -10420,7 +10825,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2)': + '@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2)': dependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 @@ -10428,62 +10833,62 @@ snapshots: '@standard-schema/spec': 1.1.0 better-call: 1.3.7(zod@4.4.3) jose: 6.2.8 - kysely: 0.29.4 + kysely: 0.29.5 nanostores: 1.4.2 zod: 4.4.3 optionalDependencies: '@cloudflare/workers-types': 4.20260702.1 '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9))': + '@better-auth/drizzle-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9))': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 optionalDependencies: - drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9) + drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9) - '@better-auth/drizzle-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))': + '@better-auth/drizzle-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 optionalDependencies: - drizzle-orm: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + drizzle-orm: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) - '@better-auth/kysely-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4)': + '@better-auth/kysely-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.5)': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 optionalDependencies: - kysely: 0.29.4 + kysely: 0.29.5 - '@better-auth/memory-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/memory-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/mongo-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/prisma-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': + '@better-auth/prisma-adapter@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/telemetry@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + '@better-auth/telemetry@1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@better-auth/utils@0.4.2': dependencies: - '@noble/hashes': 2.2.0 + '@noble/hashes': 2.3.0 '@better-auth/utils@0.4.3': dependencies: - '@noble/hashes': 2.2.0 + '@noble/hashes': 2.3.0 '@better-fetch/fetch@1.3.1': {} @@ -10546,7 +10951,7 @@ snapshots: dependencies: '@emnapi/core': 1.11.1 '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true '@bruits/satteri-win32-arm64-msvc@0.9.5': @@ -10573,26 +10978,26 @@ snapshots: '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1)': + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260811.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260730.1 + workerd: 1.20260811.1 - '@cloudflare/unenv-preset@2.7.7(unenv@2.0.0-rc.21)(workerd@1.20260730.1)': + '@cloudflare/unenv-preset@2.7.7(unenv@2.0.0-rc.21)(workerd@1.20260811.1)': dependencies: unenv: 2.0.0-rc.21 optionalDependencies: - workerd: 1.20260730.1 + workerd: 1.20260811.1 - '@cloudflare/vite-plugin@1.50.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1))': + '@cloudflare/vite-plugin@1.52.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1))': dependencies: - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1) - miniflare: 5.20260730.0-alpha + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260811.1) + miniflare: 5.20260811.0-alpha unenv: 2.0.0-rc.24 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) - workerd: 1.20260730.1 - wrangler: 4.118.0(@cloudflare/workers-types@4.20260702.1) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + workerd: 1.20260811.1 + wrangler: 4.122.0(@cloudflare/workers-types@4.20260702.1) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -10601,18 +11006,33 @@ snapshots: '@cloudflare/workerd-darwin-64@1.20260730.1': optional: true + '@cloudflare/workerd-darwin-64@1.20260811.1': + optional: true + '@cloudflare/workerd-darwin-arm64@1.20260730.1': optional: true + '@cloudflare/workerd-darwin-arm64@1.20260811.1': + optional: true + '@cloudflare/workerd-linux-64@1.20260730.1': optional: true + '@cloudflare/workerd-linux-64@1.20260811.1': + optional: true + '@cloudflare/workerd-linux-arm64@1.20260730.1': optional: true + '@cloudflare/workerd-linux-arm64@1.20260811.1': + optional: true + '@cloudflare/workerd-windows-64@1.20260730.1': optional: true + '@cloudflare/workerd-windows-64@1.20260811.1': + optional: true + '@cloudflare/workers-types@4.20260702.1': {} '@cspotcode/source-map-support@0.8.1': @@ -10621,6 +11041,15 @@ snapshots: '@date-fns/tz@1.5.0': {} + '@distilled.cloud/core@1.0.0-rc.4(effect@4.0.0-beta.107)': + dependencies: + effect: 4.0.0-beta.107 + + '@distilled.cloud/github@1.0.0-rc.4(effect@4.0.0-beta.107)': + dependencies: + '@distilled.cloud/core': 1.0.0-rc.4(effect@4.0.0-beta.107) + effect: 4.0.0-beta.107 + '@dnd-kit/abstract@0.3.2': dependencies: '@dnd-kit/geometry': 0.3.2 @@ -10683,51 +11112,51 @@ snapshots: '@drizzle-team/brocli@0.12.0': {} - '@effect-aws/client-s3@2.0.0-beta.6(effect@4.0.0-beta.103)': + '@effect-aws/client-s3@2.0.0-beta.6(effect@4.0.0-beta.107)': dependencies: - '@aws-sdk/client-s3': 3.1103.0 - '@aws-sdk/s3-request-presigner': 3.1103.0 - '@aws-sdk/types': 3.974.2 - '@effect-aws/commons': 1.0.0-beta.4(effect@4.0.0-beta.103) - effect: 4.0.0-beta.103 + '@aws-sdk/client-s3': 3.1109.0 + '@aws-sdk/s3-request-presigner': 3.1109.0 + '@aws-sdk/types': 3.974.3 + '@effect-aws/commons': 1.0.0-beta.4(effect@4.0.0-beta.107) + effect: 4.0.0-beta.107 - '@effect-aws/commons@1.0.0-beta.4(effect@4.0.0-beta.103)': + '@effect-aws/commons@1.0.0-beta.4(effect@4.0.0-beta.107)': dependencies: - '@smithy/protocol-http': 5.5.16 - '@smithy/smithy-client': 4.14.16 - '@smithy/types': 4.16.1 - effect: 4.0.0-beta.103 + '@smithy/protocol-http': 5.6.0 + '@smithy/smithy-client': 4.15.0 + '@smithy/types': 4.17.0 + effect: 4.0.0-beta.107 - '@effect-aws/s3@1.0.0-beta.5(@effect-aws/client-s3@2.0.0-beta.6(effect@4.0.0-beta.103))(effect@4.0.0-beta.103)': + '@effect-aws/s3@1.0.0-beta.5(@effect-aws/client-s3@2.0.0-beta.6(effect@4.0.0-beta.107))(effect@4.0.0-beta.107)': dependencies: - '@effect-aws/client-s3': 2.0.0-beta.6(effect@4.0.0-beta.103) - effect: 4.0.0-beta.103 + '@effect-aws/client-s3': 2.0.0-beta.6(effect@4.0.0-beta.107) + effect: 4.0.0-beta.107 - '@effect/ai-openai@4.0.0-beta.66(effect@4.0.0-beta.103)': + '@effect/ai-openai@4.0.0-beta.107(effect@4.0.0-beta.107)': dependencies: - effect: 4.0.0-beta.103 + effect: 4.0.0-beta.107 - '@effect/atom-react@4.0.0-beta.66(effect@4.0.0-beta.103)(react@19.2.8)(scheduler@0.27.0)': + '@effect/atom-react@4.0.0-beta.107(effect@4.0.0-beta.107)(react@19.2.8)(scheduler@0.27.0)': dependencies: - effect: 4.0.0-beta.103 + effect: 4.0.0-beta.107 react: 19.2.8 scheduler: 0.27.0 '@effect/language-service@0.85.1': {} - '@effect/platform-node-shared@4.0.0-beta.103(effect@4.0.0-beta.103)': + '@effect/platform-node-shared@4.0.0-beta.107(effect@4.0.0-beta.107)': dependencies: '@types/ws': 8.18.1 - effect: 4.0.0-beta.103 - ws: 8.21.2 + effect: 4.0.0-beta.107 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate - '@effect/platform-node@4.0.0-beta.103(effect@4.0.0-beta.103)(ioredis@5.11.1)': + '@effect/platform-node@4.0.0-beta.107(effect@4.0.0-beta.107)(ioredis@5.11.1)': dependencies: - '@effect/platform-node-shared': 4.0.0-beta.103(effect@4.0.0-beta.103) - effect: 4.0.0-beta.103 + '@effect/platform-node-shared': 4.0.0-beta.107(effect@4.0.0-beta.107) + effect: 4.0.0-beta.107 ioredis: 5.11.1 mime: 4.1.0 undici: 8.10.0 @@ -10735,26 +11164,31 @@ snapshots: - bufferutil - utf-8-validate - '@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103)': + '@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107)': dependencies: - effect: 4.0.0-beta.103 - pg: 8.22.0 + effect: 4.0.0-beta.107 + pg: 8.23.0 pg-connection-string: 2.14.0 - pg-cursor: 2.21.0(pg@8.22.0) - pg-pool: 3.14.0(pg@8.22.0) + pg-cursor: 2.22.0(pg@8.23.0) + pg-pool: 3.14.0(pg@8.23.0) pg-types: 4.1.0 transitivePeerDependencies: - pg-native - '@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103)': + '@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107)': dependencies: '@electric-sql/pglite': 0.5.4 - effect: 4.0.0-beta.103 + effect: 4.0.0-beta.107 - '@effect/vitest@4.0.0-beta.94(effect@4.0.0-beta.103)(vitest@4.1.10)': + '@effect/vitest@4.0.0-beta.107(effect@4.0.0-beta.107)(vitest@4.1.10)': dependencies: - effect: 4.0.0-beta.103 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + effect: 4.0.0-beta.107 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + + '@effect/vitest@4.0.0-beta.94(effect@4.0.0-beta.107)(vitest@4.1.10)': + dependencies: + effect: 4.0.0-beta.107 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@egoist/tailwindcss-icons@1.9.2(tailwindcss@4.3.3)': dependencies: @@ -10838,6 +11272,9 @@ snapshots: '@esbuild/aix-ppc64@0.28.1': optional: true + '@esbuild/aix-ppc64@0.28.2': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true @@ -10847,6 +11284,9 @@ snapshots: '@esbuild/android-arm64@0.28.1': optional: true + '@esbuild/android-arm64@0.28.2': + optional: true + '@esbuild/android-arm@0.25.12': optional: true @@ -10856,6 +11296,9 @@ snapshots: '@esbuild/android-arm@0.28.1': optional: true + '@esbuild/android-arm@0.28.2': + optional: true + '@esbuild/android-x64@0.25.12': optional: true @@ -10865,6 +11308,9 @@ snapshots: '@esbuild/android-x64@0.28.1': optional: true + '@esbuild/android-x64@0.28.2': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true @@ -10874,6 +11320,9 @@ snapshots: '@esbuild/darwin-arm64@0.28.1': optional: true + '@esbuild/darwin-arm64@0.28.2': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true @@ -10883,6 +11332,9 @@ snapshots: '@esbuild/darwin-x64@0.28.1': optional: true + '@esbuild/darwin-x64@0.28.2': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true @@ -10892,6 +11344,9 @@ snapshots: '@esbuild/freebsd-arm64@0.28.1': optional: true + '@esbuild/freebsd-arm64@0.28.2': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true @@ -10901,6 +11356,9 @@ snapshots: '@esbuild/freebsd-x64@0.28.1': optional: true + '@esbuild/freebsd-x64@0.28.2': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true @@ -10910,6 +11368,9 @@ snapshots: '@esbuild/linux-arm64@0.28.1': optional: true + '@esbuild/linux-arm64@0.28.2': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true @@ -10919,6 +11380,9 @@ snapshots: '@esbuild/linux-arm@0.28.1': optional: true + '@esbuild/linux-arm@0.28.2': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true @@ -10928,6 +11392,9 @@ snapshots: '@esbuild/linux-ia32@0.28.1': optional: true + '@esbuild/linux-ia32@0.28.2': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true @@ -10937,6 +11404,9 @@ snapshots: '@esbuild/linux-loong64@0.28.1': optional: true + '@esbuild/linux-loong64@0.28.2': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true @@ -10946,6 +11416,9 @@ snapshots: '@esbuild/linux-mips64el@0.28.1': optional: true + '@esbuild/linux-mips64el@0.28.2': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true @@ -10955,6 +11428,9 @@ snapshots: '@esbuild/linux-ppc64@0.28.1': optional: true + '@esbuild/linux-ppc64@0.28.2': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true @@ -10964,6 +11440,9 @@ snapshots: '@esbuild/linux-riscv64@0.28.1': optional: true + '@esbuild/linux-riscv64@0.28.2': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true @@ -10973,6 +11452,9 @@ snapshots: '@esbuild/linux-s390x@0.28.1': optional: true + '@esbuild/linux-s390x@0.28.2': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true @@ -10982,6 +11464,9 @@ snapshots: '@esbuild/linux-x64@0.28.1': optional: true + '@esbuild/linux-x64@0.28.2': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true @@ -10991,6 +11476,9 @@ snapshots: '@esbuild/netbsd-arm64@0.28.1': optional: true + '@esbuild/netbsd-arm64@0.28.2': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true @@ -11000,6 +11488,9 @@ snapshots: '@esbuild/netbsd-x64@0.28.1': optional: true + '@esbuild/netbsd-x64@0.28.2': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true @@ -11009,6 +11500,9 @@ snapshots: '@esbuild/openbsd-arm64@0.28.1': optional: true + '@esbuild/openbsd-arm64@0.28.2': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true @@ -11018,6 +11512,9 @@ snapshots: '@esbuild/openbsd-x64@0.28.1': optional: true + '@esbuild/openbsd-x64@0.28.2': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true @@ -11027,6 +11524,9 @@ snapshots: '@esbuild/openharmony-arm64@0.28.1': optional: true + '@esbuild/openharmony-arm64@0.28.2': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true @@ -11036,6 +11536,9 @@ snapshots: '@esbuild/sunos-x64@0.28.1': optional: true + '@esbuild/sunos-x64@0.28.2': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true @@ -11045,6 +11548,9 @@ snapshots: '@esbuild/win32-arm64@0.28.1': optional: true + '@esbuild/win32-arm64@0.28.2': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true @@ -11054,6 +11560,9 @@ snapshots: '@esbuild/win32-ia32@0.28.1': optional: true + '@esbuild/win32-ia32@0.28.2': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true @@ -11063,6 +11572,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@esbuild/win32-x64@0.28.2': + optional: true + '@faker-js/faker@10.5.0': {} '@floating-ui/core@1.8.0': @@ -11082,9 +11594,9 @@ snapshots: '@floating-ui/utils@0.2.12': {} - '@hono/node-server@2.1.0(hono@4.13.0)': + '@hono/node-server@2.1.0(hono@4.13.1)': dependencies: - hono: 4.13.0 + hono: 4.13.1 '@hugeicons/core-free-icons@4.2.3': {} @@ -11094,7 +11606,7 @@ snapshots: '@iarna/toml@2.2.5': {} - '@iconify-json/lucide@1.2.121': + '@iconify-json/lucide@1.2.123': dependencies: '@iconify/types': 2.0.0 @@ -11417,7 +11929,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - '@inlang/paraglide-js@2.23.1(typescript@6.0.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))': + '@inlang/paraglide-js@2.23.2(typescript@6.0.3)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@inlang/recommend-sherlock': 0.2.1 '@inlang/sdk': 2.10.2 @@ -11428,7 +11940,7 @@ snapshots: urlpattern-polyfill: 10.1.0 optionalDependencies: typescript: 6.0.3 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - babel-plugin-macros @@ -11456,12 +11968,12 @@ snapshots: '@types/node': 24.13.3 optional: true - '@inquirer/confirm@6.1.1(@types/node@26.1.2)': + '@inquirer/confirm@6.1.1(@types/node@26.2.0)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.2) - '@inquirer/type': 4.0.7(@types/node@26.1.2) + '@inquirer/core': 11.2.1(@types/node@26.2.0) + '@inquirer/type': 4.0.7(@types/node@26.2.0) optionalDependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@inquirer/core@11.2.1(@types/node@24.13.3)': dependencies: @@ -11476,17 +11988,17 @@ snapshots: '@types/node': 24.13.3 optional: true - '@inquirer/core@11.2.1(@types/node@26.1.2)': + '@inquirer/core@11.2.1(@types/node@26.2.0)': dependencies: '@inquirer/ansi': 2.0.7 '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.1.2) + '@inquirer/type': 4.0.7(@types/node@26.2.0) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@inquirer/figures@2.0.7': {} @@ -11495,9 +12007,9 @@ snapshots: '@types/node': 24.13.3 optional: true - '@inquirer/type@4.0.7(@types/node@26.1.2)': + '@inquirer/type@4.0.7(@types/node@26.2.0)': optionalDependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@ioredis/commands@1.10.0': {} @@ -11545,7 +12057,7 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - '@ladle/react@5.1.1(@types/node@26.1.2)(@types/react@19.2.18)(jiti@2.7.0)(lightningcss@1.33.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.6)(typescript@6.0.3)(yaml@2.9.0)': + '@ladle/react@5.1.1(@types/node@26.2.0)(@types/react@19.2.18)(jiti@2.7.0)(lightningcss@1.33.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tsx@4.23.12)(typescript@6.0.3)(yaml@2.9.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7 @@ -11557,9 +12069,9 @@ snapshots: '@ladle/react-context': 1.0.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@mdx-js/mdx': 3.1.1 '@mdx-js/react': 3.1.1(@types/react@19.2.18)(react@19.2.8) - '@vitejs/plugin-react': 4.7.0(vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0)) - '@vitejs/plugin-react-swc': 3.11.0(vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0)) - axe-core: 4.12.1 + '@vitejs/plugin-react': 4.7.0(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0)) + '@vitejs/plugin-react-swc': 3.11.0(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0)) + axe-core: 4.13.0 boxen: 8.0.1 chokidar: 4.0.3 classnames: 2.5.1 @@ -11571,11 +12083,11 @@ snapshots: history: 5.3.0 koa: 2.16.4 lodash.merge: 4.6.2 - msw: 2.15.0(@types/node@26.1.2)(typescript@6.0.3) + msw: 2.15.0(@types/node@26.2.0)(typescript@6.0.3) open: 10.2.0 prism-react-renderer: 2.4.1(react@19.2.8) prop-types: 15.8.1 - query-string: 9.4.1 + query-string: 9.5.0 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) react-hotkeys-hook: 4.6.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -11585,8 +12097,8 @@ snapshots: remark-gfm: 4.0.1 source-map: 0.7.6 vfile: 6.0.3 - vite: 6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0) - vite-tsconfig-paths: 5.1.4(typescript@6.0.3)(vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0)) + vite: 6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0) + vite-tsconfig-paths: 5.1.4(typescript@6.0.3)(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - '@swc/helpers' - '@types/node' @@ -11620,7 +12132,7 @@ snapshots: '@lix-js/server-protocol-schema@0.1.1': {} - '@marsidev/react-turnstile@1.5.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@marsidev/react-turnstile@1.6.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -11663,17 +12175,17 @@ snapshots: '@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)': dependencies: - '@hono/node-server': 2.1.0(hono@4.13.0) + '@hono/node-server': 2.1.0(hono@4.13.1) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 - eventsource-parser: 3.1.0 + eventsource-parser: 3.1.1 express: 5.2.1 express-rate-limit: 8.6.2(express@5.2.1) - hono: 4.13.0 + hono: 4.13.1 jose: 6.2.8 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -11713,21 +12225,21 @@ snapshots: '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 '@emnapi/runtime': 1.11.1 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)': + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)': dependencies: '@emnapi/core': 1.11.1 '@emnapi/runtime': 1.11.3 '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: '@emnapi/core': 1.9.2 '@emnapi/runtime': 1.9.2 @@ -11786,9 +12298,9 @@ snapshots: '@next/swc-win32-x64-msvc@16.2.6': optional: true - '@noble/ciphers@2.2.0': {} + '@noble/ciphers@2.3.0': {} - '@noble/hashes@2.2.0': {} + '@noble/hashes@2.3.0': {} '@nodable/entities@3.0.0': {} @@ -11926,7 +12438,7 @@ snapshots: '@oxc-project/types@0.126.0': {} - '@oxc-project/types@0.142.0': {} + '@oxc-project/types@0.144.0': {} '@pkgjs/parseargs@0.11.0': optional: true @@ -11935,11 +12447,11 @@ snapshots: dependencies: playwright: 1.62.1 - '@polar-sh/better-auth@1.8.4(@polar-sh/sdk@0.47.1)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@stripe/stripe-js@7.9.0)(better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10))(react@19.2.8)(zod@4.4.3)': + '@polar-sh/better-auth@1.8.4(@polar-sh/sdk@0.47.1)(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@stripe/stripe-js@7.9.0)(better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10))(react@19.2.8)(zod@4.4.3)': dependencies: '@polar-sh/checkout': 0.2.1(@stripe/react-stripe-js@4.0.2(@stripe/stripe-js@7.9.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@stripe/stripe-js@7.9.0)(react@19.2.8) '@polar-sh/sdk': 0.47.1 - better-auth: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) + better-auth: 1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10) zod: 4.4.3 transitivePeerDependencies: - '@stripe/react-stripe-js' @@ -11972,30 +12484,30 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@posthog/browser-common@0.3.1': + '@posthog/browser-common@0.5.0': dependencies: - '@posthog/core': 1.46.7 - '@posthog/types': 1.401.0 + '@posthog/core': 1.47.1 + '@posthog/types': 1.403.0 - '@posthog/core@1.46.7': + '@posthog/core@1.47.1': dependencies: - '@posthog/types': 1.401.0 + '@posthog/types': 1.403.0 - '@posthog/react@1.10.3(@types/react@19.2.18)(posthog-js@1.411.0)(react@19.2.8)': + '@posthog/react@1.10.3(@types/react@19.2.18)(posthog-js@1.416.0)(react@19.2.8)': dependencies: - posthog-js: 1.411.0 + posthog-js: 1.416.0 react: 19.2.8 optionalDependencies: '@types/react': 19.2.18 - '@posthog/types@1.401.0': {} + '@posthog/types@1.403.0': {} '@preact/signals-core@1.14.4': {} - '@prosekit/basic@0.9.5(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/basic@0.9.5(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) - '@prosekit/extensions': 0.17.4(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/extensions': 0.17.4(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosekit/pm': 0.1.18 transitivePeerDependencies: - '@lezer/common' @@ -12027,7 +12539,7 @@ snapshots: - prosemirror-state - prosemirror-transform - '@prosekit/extensions@0.17.4(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/extensions@0.17.4(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: '@ocavue/utils': 1.7.0 '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) @@ -12038,12 +12550,12 @@ snapshots: prosemirror-enter-rules: 0.1.6 prosemirror-flat-list: 0.6.0 prosemirror-gapcursor: 1.4.1 - prosemirror-highlight: 0.15.3(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + prosemirror-highlight: 0.15.3(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) prosemirror-math: 0.2.2 prosemirror-search: 1.1.1 prosemirror-tables: 1.8.5 server-dom-shim: 1.1.0 - shiki: 4.4.2 + shiki: 4.4.3 transitivePeerDependencies: - '@lezer/common' - '@lezer/highlight' @@ -12058,9 +12570,9 @@ snapshots: - refractor - sugar-high - '@prosekit/lit@0.6.7(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/lit@0.6.7(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) transitivePeerDependencies: - '@lezer/common' - '@lezer/highlight' @@ -12081,7 +12593,7 @@ snapshots: '@prosekit/pm@0.1.18': dependencies: - prosemirror-commands: 1.7.1 + prosemirror-commands: 1.7.2 prosemirror-history: 1.5.0 prosemirror-inputrules: 1.5.1 prosemirror-keymap: 1.2.3 @@ -12090,11 +12602,11 @@ snapshots: prosemirror-transform: 1.12.0 prosemirror-view: 1.42.2 - '@prosekit/preact@0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/preact@0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) '@prosekit/pm': 0.1.18 - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosemirror-adapter/core': 0.5.5 '@prosemirror-adapter/preact': 0.5.5(preact@10.29.8) optionalDependencies: @@ -12117,11 +12629,11 @@ snapshots: - y-prosemirror - yjs - '@prosekit/react@0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@prosekit/react@0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) '@prosekit/pm': 0.1.18 - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosemirror-adapter/core': 0.5.5 '@prosemirror-adapter/react': 0.5.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8) optionalDependencies: @@ -12145,11 +12657,11 @@ snapshots: - y-prosemirror - yjs - '@prosekit/solid@0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(solid-js@1.9.14)': + '@prosekit/solid@0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(solid-js@1.9.14)': dependencies: '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) '@prosekit/pm': 0.1.18 - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosemirror-adapter/core': 0.5.5 '@prosemirror-adapter/solid': 0.5.5(solid-js@1.9.14) optionalDependencies: @@ -12172,11 +12684,11 @@ snapshots: - y-prosemirror - yjs - '@prosekit/svelte@0.9.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/svelte@0.9.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) '@prosekit/pm': 0.1.18 - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosemirror-adapter/core': 0.5.5 '@prosemirror-adapter/svelte': 0.5.5 transitivePeerDependencies: @@ -12197,11 +12709,11 @@ snapshots: - y-prosemirror - yjs - '@prosekit/vue@0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/vue@0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) '@prosekit/pm': 0.1.18 - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosemirror-adapter/core': 0.5.5 '@prosemirror-adapter/vue': 0.5.5 transitivePeerDependencies: @@ -12222,15 +12734,15 @@ snapshots: - y-prosemirror - yjs - '@prosekit/web@0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': + '@prosekit/web@0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)': dependencies: '@aria-ui/core': 0.2.1 - '@aria-ui/elements': 0.1.12 + '@aria-ui/elements': 0.1.13 '@aria-ui/utils': 0.1.7 '@floating-ui/dom': 1.8.0 '@ocavue/utils': 1.7.0 '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) - '@prosekit/extensions': 0.17.4(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/extensions': 0.17.4(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosekit/pm': 0.1.18 prosemirror-tables: 1.8.5 transitivePeerDependencies: @@ -12340,92 +12852,92 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.16': optional: true - '@rolldown/binding-android-arm64@1.2.2': + '@rolldown/binding-android-arm64@1.2.4': optional: true '@rolldown/binding-darwin-arm64@1.0.0-rc.16': optional: true - '@rolldown/binding-darwin-arm64@1.2.2': + '@rolldown/binding-darwin-arm64@1.2.4': optional: true '@rolldown/binding-darwin-x64@1.0.0-rc.16': optional: true - '@rolldown/binding-darwin-x64@1.2.2': + '@rolldown/binding-darwin-x64@1.2.4': optional: true '@rolldown/binding-freebsd-x64@1.0.0-rc.16': optional: true - '@rolldown/binding-freebsd-x64@1.2.2': + '@rolldown/binding-freebsd-x64@1.2.4': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.2': + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.2': + '@rolldown/binding-linux-arm64-gnu@1.2.4': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.2': + '@rolldown/binding-linux-arm64-musl@1.2.4': optional: true '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.2': + '@rolldown/binding-linux-ppc64-gnu@1.2.4': optional: true '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.2': + '@rolldown/binding-linux-s390x-gnu@1.2.4': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.2': + '@rolldown/binding-linux-x64-gnu@1.2.4': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': optional: true - '@rolldown/binding-linux-x64-musl@1.2.2': + '@rolldown/binding-linux-x64-musl@1.2.4': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': optional: true - '@rolldown/binding-openharmony-arm64@1.2.2': + '@rolldown/binding-openharmony-arm64@1.2.4': optional: true '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': dependencies: '@emnapi/core': 1.9.2 '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.2': + '@rolldown/binding-win32-arm64-msvc@1.2.4': optional: true '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.2': + '@rolldown/binding-win32-x64-msvc@1.2.4': optional: true '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -12436,14 +12948,6 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rollup/pluginutils@5.4.0(rollup@4.62.4)': - dependencies: - '@types/estree': 1.0.9 - estree-walker: 2.0.2 - picomatch: 4.0.5 - optionalDependencies: - rollup: 4.62.4 - '@rollup/rollup-android-arm-eabi@4.62.4': optional: true @@ -12526,32 +13030,32 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 - '@sentry/browser-utils@10.69.0': + '@sentry/browser-utils@10.70.0': dependencies: '@sentry/conventions': 0.16.0 - '@sentry/core': 10.69.0 + '@sentry/core': 10.70.0 - '@sentry/browser@10.69.0': + '@sentry/browser@10.70.0': dependencies: - '@sentry/browser-utils': 10.69.0 + '@sentry/browser-utils': 10.70.0 '@sentry/conventions': 0.16.0 - '@sentry/core': 10.69.0 - '@sentry/feedback': 10.69.0 - '@sentry/replay': 10.69.0 - '@sentry/replay-canvas': 10.69.0 + '@sentry/core': 10.70.0 + '@sentry/feedback': 10.70.0 + '@sentry/replay': 10.70.0 + '@sentry/replay-canvas': 10.70.0 '@sentry/conventions@0.16.0': {} - '@sentry/core@10.69.0': + '@sentry/core@10.70.0': dependencies: '@sentry/conventions': 0.16.0 - '@sentry/effect@10.69.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(effect@4.0.0-beta.103)': + '@sentry/effect@10.70.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))(effect@4.0.0-beta.107)': dependencies: - '@sentry/browser': 10.69.0 - '@sentry/core': 10.69.0 - '@sentry/node-core': 10.69.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) - effect: 4.0.0-beta.103 + '@sentry/browser': 10.70.0 + '@sentry/core': 10.70.0 + '@sentry/node-core': 10.70.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) + effect: 4.0.0-beta.107 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/core' @@ -12559,73 +13063,73 @@ snapshots: - '@opentelemetry/instrumentation' - '@opentelemetry/sdk-trace-base' - '@sentry/feedback@10.69.0': + '@sentry/feedback@10.70.0': dependencies: - '@sentry/core': 10.69.0 + '@sentry/core': 10.70.0 - '@sentry/node-core@10.69.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))': + '@sentry/node-core@10.70.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))': dependencies: '@sentry/conventions': 0.16.0 - '@sentry/core': 10.69.0 - '@sentry/opentelemetry': 10.69.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) + '@sentry/core': 10.70.0 + '@sentry/opentelemetry': 10.70.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1)) import-in-the-middle: 3.3.3 optionalDependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) - '@sentry/opentelemetry@10.69.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))': + '@sentry/opentelemetry@10.70.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.10.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.10.0(@opentelemetry/api@1.9.1))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.10.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.10.0(@opentelemetry/api@1.9.1) '@sentry/conventions': 0.16.0 - '@sentry/core': 10.69.0 + '@sentry/core': 10.70.0 - '@sentry/replay-canvas@10.69.0': + '@sentry/replay-canvas@10.70.0': dependencies: - '@sentry/core': 10.69.0 - '@sentry/replay': 10.69.0 + '@sentry/core': 10.70.0 + '@sentry/replay': 10.70.0 - '@sentry/replay@10.69.0': + '@sentry/replay@10.70.0': dependencies: - '@sentry/browser-utils': 10.69.0 - '@sentry/core': 10.69.0 + '@sentry/browser-utils': 10.70.0 + '@sentry/core': 10.70.0 - '@shikijs/core@4.4.2': + '@shikijs/core@4.4.3': dependencies: - '@shikijs/primitive': 4.4.2 - '@shikijs/types': 4.4.2 + '@shikijs/primitive': 4.4.3 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@4.4.2': + '@shikijs/engine-javascript@4.4.3': dependencies: - '@shikijs/types': 4.4.2 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.6 - '@shikijs/engine-oniguruma@4.4.2': + '@shikijs/engine-oniguruma@4.4.3': dependencies: - '@shikijs/types': 4.4.2 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@4.4.2': + '@shikijs/langs@4.4.3': dependencies: - '@shikijs/types': 4.4.2 + '@shikijs/types': 4.4.3 - '@shikijs/primitive@4.4.2': + '@shikijs/primitive@4.4.3': dependencies: - '@shikijs/types': 4.4.2 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 - '@shikijs/themes@4.4.2': + '@shikijs/themes@4.4.3': dependencies: - '@shikijs/types': 4.4.2 + '@shikijs/types': 4.4.3 - '@shikijs/types@4.4.2': + '@shikijs/types@4.4.3': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 @@ -12640,52 +13144,52 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@smithy/core@3.31.1': + '@smithy/core@3.32.0': dependencies: - '@smithy/types': 4.16.1 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.4.16': + '@smithy/credential-provider-imds@4.5.0': dependencies: - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.6.13': + '@smithy/fetch-http-handler@5.7.0': dependencies: - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@smithy/node-config-provider@4.5.16': + '@smithy/node-config-provider@4.6.0': dependencies: - '@smithy/core': 3.31.1 + '@smithy/core': 3.32.0 tslib: 2.8.1 - '@smithy/node-http-handler@4.9.13': + '@smithy/node-http-handler@4.10.0': dependencies: - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@smithy/protocol-http@5.5.16': + '@smithy/protocol-http@5.6.0': dependencies: - '@smithy/core': 3.31.1 + '@smithy/core': 3.32.0 tslib: 2.8.1 - '@smithy/signature-v4@5.6.12': + '@smithy/signature-v4@5.7.0': dependencies: - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@smithy/smithy-client@4.14.16': + '@smithy/smithy-client@4.15.0': dependencies: - '@smithy/core': 3.31.1 - '@smithy/types': 4.16.1 + '@smithy/core': 3.32.0 + '@smithy/types': 4.17.0 tslib: 2.8.1 - '@smithy/types@4.16.1': + '@smithy/types@4.17.0': dependencies: tslib: 2.8.1 @@ -12729,7 +13233,7 @@ snapshots: dependencies: solid-js: 1.9.14 - '@speed-highlight/core@1.2.23': {} + '@speed-highlight/core@1.2.24': {} '@sqlite.org/sqlite-wasm@3.48.0-build4': {} @@ -12876,62 +13380,62 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.3.3 - '@tailwindcss/vite@4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) - '@takumi-rs/core-darwin-arm64@2.5.7': + '@takumi-rs/core-darwin-arm64@2.7.2': optional: true - '@takumi-rs/core-darwin-x64@2.5.7': + '@takumi-rs/core-darwin-x64@2.7.2': optional: true - '@takumi-rs/core-linux-arm64-gnu@2.5.7': + '@takumi-rs/core-linux-arm64-gnu@2.7.2': optional: true - '@takumi-rs/core-linux-arm64-musl@2.5.7': + '@takumi-rs/core-linux-arm64-musl@2.7.2': optional: true - '@takumi-rs/core-linux-x64-gnu@2.5.7': + '@takumi-rs/core-linux-x64-gnu@2.7.2': optional: true - '@takumi-rs/core-linux-x64-musl@2.5.7': + '@takumi-rs/core-linux-x64-musl@2.7.2': optional: true - '@takumi-rs/core-win32-arm64-msvc@2.5.7': + '@takumi-rs/core-win32-arm64-msvc@2.7.2': optional: true - '@takumi-rs/core-win32-x64-msvc@2.5.7': + '@takumi-rs/core-win32-x64-msvc@2.7.2': optional: true - '@takumi-rs/core@2.5.7(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)': + '@takumi-rs/core@2.7.2(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)': dependencies: - '@takumi-rs/helpers': 2.5.7(preact@10.29.8)(react@19.2.8) + '@takumi-rs/helpers': 2.7.2(preact@10.29.8)(react@19.2.8) optionalDependencies: - '@takumi-rs/core-darwin-arm64': 2.5.7 - '@takumi-rs/core-darwin-x64': 2.5.7 - '@takumi-rs/core-linux-arm64-gnu': 2.5.7 - '@takumi-rs/core-linux-arm64-musl': 2.5.7 - '@takumi-rs/core-linux-x64-gnu': 2.5.7 - '@takumi-rs/core-linux-x64-musl': 2.5.7 - '@takumi-rs/core-win32-arm64-msvc': 2.5.7 - '@takumi-rs/core-win32-x64-msvc': 2.5.7 + '@takumi-rs/core-darwin-arm64': 2.7.2 + '@takumi-rs/core-darwin-x64': 2.7.2 + '@takumi-rs/core-linux-arm64-gnu': 2.7.2 + '@takumi-rs/core-linux-arm64-musl': 2.7.2 + '@takumi-rs/core-linux-x64-gnu': 2.7.2 + '@takumi-rs/core-linux-x64-musl': 2.7.2 + '@takumi-rs/core-win32-arm64-msvc': 2.7.2 + '@takumi-rs/core-win32-x64-msvc': 2.7.2 csstype: 3.2.3 transitivePeerDependencies: - preact - react - '@takumi-rs/helpers@2.5.7(preact@10.29.8)(react@19.2.8)': + '@takumi-rs/helpers@2.7.2(preact@10.29.8)(react@19.2.8)': optionalDependencies: preact: 10.29.8 react: 19.2.8 - '@takumi-rs/wasm@2.5.7(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)': + '@takumi-rs/wasm@2.7.2(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)': dependencies: - '@takumi-rs/helpers': 2.5.7(preact@10.29.8)(react@19.2.8) + '@takumi-rs/helpers': 2.7.2(preact@10.29.8)(react@19.2.8) optionalDependencies: csstype: 3.2.3 transitivePeerDependencies: @@ -12944,7 +13448,14 @@ snapshots: sorted-btree: 1.8.1 typescript: 6.0.3 - '@tanstack/db@0.6.17(typescript@6.0.3)': + '@tanstack/db@0.7.0(typescript@6.0.3)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@tanstack/db-ivm': 0.1.18(typescript@6.0.3) + '@tanstack/pacer-lite': 0.2.2 + typescript: 6.0.3 + + '@tanstack/db@0.7.2(typescript@6.0.3)': dependencies: '@standard-schema/spec': 1.1.0 '@tanstack/db-ivm': 0.1.18(typescript@6.0.3) @@ -12957,7 +13468,7 @@ snapshots: '@tanstack/devtools-event-bus@0.4.1': dependencies: - ws: 8.21.2 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -13005,17 +13516,17 @@ snapshots: - csstype - utf-8-validate - '@tanstack/form-core@1.33.3': + '@tanstack/form-core@1.33.5': dependencies: '@tanstack/devtools-event-client': 0.4.4 '@tanstack/pacer-lite': 0.1.1 - '@tanstack/store': 0.11.0 + '@tanstack/store': 0.11.1 - '@tanstack/form-devtools@0.2.32(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14)': + '@tanstack/form-devtools@0.2.34(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14)': dependencies: '@tanstack/devtools-ui': 0.5.3(csstype@3.2.3)(solid-js@1.9.14) '@tanstack/devtools-utils': 0.4.0(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14) - '@tanstack/form-core': 1.33.3 + '@tanstack/form-core': 1.33.5 clsx: 2.1.1 dayjs: 1.11.21 goober: 2.1.19(csstype@3.2.3) @@ -13027,7 +13538,7 @@ snapshots: - react - vue - '@tanstack/history@1.162.0': {} + '@tanstack/history@1.162.1': {} '@tanstack/pacer-lite@0.1.1': {} @@ -13040,18 +13551,25 @@ snapshots: '@tanstack/query-core@5.101.4': {} - '@tanstack/query-db-collection@1.2.1(@tanstack/query-core@5.101.4)(typescript@6.0.3)': + '@tanstack/query-db-collection@1.2.2(@tanstack/query-core@5.101.4)(typescript@6.0.3)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@tanstack/db': 0.7.0(typescript@6.0.3) + '@tanstack/query-core': 5.101.4 + typescript: 6.0.3 + + '@tanstack/query-db-collection@1.2.4(@tanstack/query-core@5.101.4)(typescript@6.0.3)': dependencies: '@standard-schema/spec': 1.1.0 - '@tanstack/db': 0.6.17(typescript@6.0.3) + '@tanstack/db': 0.7.2(typescript@6.0.3) '@tanstack/query-core': 5.101.4 typescript: 6.0.3 '@tanstack/query-devtools@5.101.4': {} - '@tanstack/react-db@0.1.95(react@19.2.8)(typescript@6.0.3)': + '@tanstack/react-db@0.1.96(react@19.2.8)(typescript@6.0.3)': dependencies: - '@tanstack/db': 0.6.17(typescript@6.0.3) + '@tanstack/db': 0.7.0(typescript@6.0.3) react: 19.2.8 use-sync-external-store: 1.6.0(react@19.2.8) transitivePeerDependencies: @@ -13070,10 +13588,10 @@ snapshots: - solid-js - utf-8-validate - '@tanstack/react-form-devtools@0.2.32(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14)': + '@tanstack/react-form-devtools@0.2.34(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14)': dependencies: '@tanstack/devtools-utils': 0.4.0(@types/react@19.2.18)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14) - '@tanstack/form-devtools': 0.2.32(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14) + '@tanstack/form-devtools': 0.2.34(@types/react@19.2.18)(csstype@3.2.3)(preact@10.29.8)(react@19.2.8)(solid-js@1.9.14) react: 19.2.8 transitivePeerDependencies: - '@types/react' @@ -13082,10 +13600,10 @@ snapshots: - solid-js - vue - '@tanstack/react-form@1.33.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-form@1.33.5(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@tanstack/form-core': 1.33.3 - '@tanstack/react-store': 0.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@tanstack/form-core': 1.33.5 + '@tanstack/react-store': 0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 transitivePeerDependencies: - react-dom @@ -13108,29 +13626,29 @@ snapshots: '@tanstack/query-core': 5.101.4 react: 19.2.8 - '@tanstack/react-router-devtools@1.167.1(@tanstack/react-router@1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@tanstack/router-core@1.171.16)(csstype@3.2.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-router-devtools@1.167.1(@tanstack/react-router@1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(@tanstack/router-core@1.171.22)(csstype@3.2.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@tanstack/react-router': 1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tanstack/router-devtools-core': 1.168.1(@tanstack/router-core@1.171.16)(csstype@3.2.3) + '@tanstack/react-router': 1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@tanstack/router-devtools-core': 1.168.1(@tanstack/router-core@1.171.22)(csstype@3.2.3) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: - '@tanstack/router-core': 1.171.16 + '@tanstack/router-core': 1.171.22 transitivePeerDependencies: - csstype - '@tanstack/react-router@1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-router@1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@tanstack/history': 1.162.0 + '@tanstack/history': 1.162.1 '@tanstack/react-store': 0.9.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@tanstack/router-core': 1.171.16 + '@tanstack/router-core': 1.171.22 isbot: 5.2.1 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - '@tanstack/react-store@0.11.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-store@0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@tanstack/store': 0.11.0 + '@tanstack/store': 0.11.1 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) use-sync-external-store: 1.6.0(react@19.2.8) @@ -13155,25 +13673,25 @@ snapshots: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - '@tanstack/router-core@1.171.16': + '@tanstack/router-core@1.171.22': dependencies: - '@tanstack/history': 1.162.0 + '@tanstack/history': 1.162.1 cookie-es: 3.1.1 seroval: 1.6.2 seroval-plugins: 1.6.2(seroval@1.6.2) - '@tanstack/router-devtools-core@1.168.1(@tanstack/router-core@1.171.16)(csstype@3.2.3)': + '@tanstack/router-devtools-core@1.168.1(@tanstack/router-core@1.171.22)(csstype@3.2.3)': dependencies: - '@tanstack/router-core': 1.171.16 + '@tanstack/router-core': 1.171.22 clsx: 2.1.1 goober: 2.1.19(csstype@3.2.3) optionalDependencies: csstype: 3.2.3 - '@tanstack/router-generator@1.167.22': + '@tanstack/router-generator@1.167.28': dependencies: '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.16 + '@tanstack/router-core': 1.171.22 '@tanstack/router-utils': 1.162.2 '@tanstack/virtual-file-routes': 1.162.0 jiti: 2.7.0 @@ -13183,21 +13701,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.24(@tanstack/react-router@1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(esbuild@0.28.1)(rolldown@1.2.2)(rollup@4.62.4)(vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))': + '@tanstack/router-plugin@1.168.30(@tanstack/react-router@1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.16 - '@tanstack/router-generator': 1.167.22 + '@tanstack/router-core': 1.171.22 + '@tanstack/router-generator': 1.167.28 '@tanstack/router-utils': 1.162.2 chokidar: 5.0.0 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.2)(rollup@4.62.4)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) zod: 4.4.3 optionalDependencies: - '@tanstack/react-router': 1.170.19(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) - vite-plugin-solid: 2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@tanstack/react-router': 1.170.27(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + vite-plugin-solid: 2.11.14(solid-js@1.9.14)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -13221,7 +13739,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/store@0.11.0': {} + '@tanstack/store@0.11.1': {} '@tanstack/store@0.8.1': {} @@ -13237,22 +13755,22 @@ snapshots: minimatch: 10.2.6 path-browserify: 1.0.1 - '@turbo/darwin-64@2.10.8': + '@turbo/darwin-64@2.10.9': optional: true - '@turbo/darwin-arm64@2.10.8': + '@turbo/darwin-arm64@2.10.9': optional: true - '@turbo/linux-64@2.10.8': + '@turbo/linux-64@2.10.9': optional: true - '@turbo/linux-arm64@2.10.8': + '@turbo/linux-arm64@2.10.9': optional: true - '@turbo/windows-64@2.10.8': + '@turbo/windows-64@2.10.9': optional: true - '@turbo/windows-arm64@2.10.8': + '@turbo/windows-arm64@2.10.9': optional: true '@tybys/wasm-util@0.10.3': @@ -13288,7 +13806,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@types/debug@4.1.13': dependencies: @@ -13322,18 +13840,18 @@ snapshots: dependencies: undici-types: 7.18.2 - '@types/node@26.1.2': + '@types/node@26.2.0': dependencies: undici-types: 8.3.0 '@types/nodemailer@7.0.12': dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 - '@types/pg@8.20.4': + '@types/pg@8.21.0': dependencies: - '@types/node': 26.1.2 - pg-protocol: 1.15.0 + '@types/node': 26.2.0 + pg-protocol: 1.16.0 pg-types: 2.2.0 '@types/prismjs@1.26.6': {} @@ -13348,7 +13866,7 @@ snapshots: '@types/set-cookie-parser@2.4.10': dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@types/statuses@2.0.6': {} @@ -13365,19 +13883,19 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@ungap/structured-clone@1.3.3': {} - '@vitejs/plugin-react-swc@3.11.0(vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0))': + '@vitejs/plugin-react-swc@3.11.0(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.27 '@swc/core': 1.15.47 - vite: 6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.7.0(vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0))': + '@vitejs/plugin-react@4.7.0(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) @@ -13385,11 +13903,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@5.2.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))': + '@vitejs/plugin-react@5.2.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) @@ -13397,17 +13915,17 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10)': + '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': dependencies: - '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10) - '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) playwright: 1.62.1 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -13415,30 +13933,30 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10)': + '@vitest/browser-playwright@4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': dependencies: - '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10) - '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitest/browser': 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) playwright: 1.62.1 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10)': + '@vitest/browser@4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/utils': 4.1.10 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) - ws: 8.21.2 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + ws: 8.21.3 transitivePeerDependencies: - bufferutil - msw @@ -13446,17 +13964,17 @@ snapshots: - vite optional: true - '@vitest/browser@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10)': + '@vitest/browser@4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/utils': 4.1.10 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) - ws: 8.21.2 + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) + ws: 8.21.3 transitivePeerDependencies: - bufferutil - msw @@ -13472,23 +13990,23 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.15.0(@types/node@24.13.3)(typescript@6.0.3) - vite: 8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) - '@vitest/mocker@4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.15.0(@types/node@26.1.2)(typescript@6.0.3) - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + msw: 2.15.0(@types/node@26.2.0)(typescript@6.0.3) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) '@vitest/pretty-format@4.1.10': dependencies: @@ -13638,17 +14156,17 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - alchemy@0.82.2(@astrojs/cloudflare@14.1.7(@types/node@26.1.2)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0))(@aws-sdk/client-s3@3.1103.0)(@cloudflare/vite-plugin@1.50.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1)))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(workerd@1.20260730.1)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1)): + alchemy@0.82.2(@astrojs/cloudflare@14.2.1(@types/node@26.2.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0))(@aws-sdk/client-s3@3.1109.0)(@cloudflare/vite-plugin@1.52.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1)))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(workerd@1.20260811.1)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1)): dependencies: - '@aws-sdk/credential-providers': 3.1103.0 - '@cloudflare/unenv-preset': 2.7.7(unenv@2.0.0-rc.21)(workerd@1.20260730.1) + '@aws-sdk/credential-providers': 3.1109.0 + '@cloudflare/unenv-preset': 2.7.7(unenv@2.0.0-rc.21)(workerd@1.20260811.1) '@cloudflare/workers-types': 4.20260702.1 '@iarna/toml': 2.2.5 '@octokit/rest': 21.1.1 - '@smithy/node-config-provider': 4.5.16 - '@smithy/types': 4.16.1 + '@smithy/node-config-provider': 4.6.0 + '@smithy/types': 4.17.0 aws4fetch: 1.0.20 - drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9) + drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9) env-paths: 3.0.0 esbuild: 0.25.12 execa: 9.6.1 @@ -13667,15 +14185,15 @@ snapshots: proper-lockfile: 4.1.2 signal-exit: 4.1.0 unenv: 2.0.0-rc.21 - wrangler: 4.118.0(@cloudflare/workers-types@4.20260702.1) - ws: 8.21.2 + wrangler: 4.122.0(@cloudflare/workers-types@4.20260702.1) + ws: 8.21.3 yaml: 2.9.0 optionalDependencies: - '@astrojs/cloudflare': 14.1.7(@types/node@26.1.2)(astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0))(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0) - '@aws-sdk/client-s3': 3.1103.0 - '@cloudflare/vite-plugin': 1.50.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1)) - astro: 7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0) - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + '@astrojs/cloudflare': 14.2.1(@types/node@26.2.0)(astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1))(yaml@2.9.0) + '@aws-sdk/client-s3': 3.1109.0 + '@cloudflare/vite-plugin': 1.52.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1)) + astro: 7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - '@aws-sdk/client-rds-data' - '@electric-sql/pglite' @@ -13722,7 +14240,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.2.2: {} + ansi-regex@6.3.0: {} ansi-styles@4.3.0: dependencies: @@ -13761,7 +14279,7 @@ snapshots: - prettier-plugin-astro - typescript - astro@7.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.1.2)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(rollup@4.62.4)(tsx@4.23.6)(yaml@2.9.0): + astro@7.2.1(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.2.0)(aws4fetch@1.0.20)(ioredis@5.11.1)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: '@astrojs/compiler-rs': 0.3.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3) '@astrojs/internal-helpers': 0.10.2 @@ -13770,7 +14288,6 @@ snapshots: '@capsizecss/unpack': 4.0.1 '@clack/prompts': 1.7.0 '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) am-i-vibing: 0.4.0 aria-query: 5.3.2 axobject-query: 4.1.0 @@ -13782,7 +14299,7 @@ snapshots: diff: 8.0.4 dset: 3.1.4 es-module-lexer: 2.3.1 - esbuild: 0.28.1 + esbuild: 0.28.2 flattie: 1.1.1 fontace: 0.4.1 get-tsconfig: 5.0.0-beta.4 @@ -13791,7 +14308,7 @@ snapshots: http-cache-semantics: 4.2.0 js-yaml: 4.3.1 jsonc-parser: 3.3.1 - magic-string: 1.1.0 + magic-string: 1.1.1 magicast: 0.5.4 mrmime: 2.0.1 neotraverse: 1.0.1 @@ -13802,22 +14319,22 @@ snapshots: piccolore: 0.1.3 picomatch: 4.0.5 semver: 7.8.5 - shiki: 4.4.2 - smol-toml: 1.7.1 + shiki: 4.4.3 + smol-toml: 1.8.0 svgo: 4.0.2 tinyclip: 0.1.15 tinyexec: 1.3.0 tinyglobby: 0.2.17 ultrahtml: 1.7.0 - unifont: 0.7.4 + unifont: 0.7.5 unstorage: 1.17.5(aws4fetch@1.0.20)(ioredis@5.11.1) - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) - vitefu: 1.1.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) xxhash-wasm: 1.1.0 yargs-parser: 22.0.0 zod: 4.4.3 optionalDependencies: - sharp: 0.35.3(@types/node@26.1.2) + sharp: 0.35.3(@types/node@26.2.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -13843,7 +14360,6 @@ snapshots: - ioredis - jiti - less - - rollup - sass - sass-embedded - stylus @@ -13862,7 +14378,7 @@ snapshots: aws4fetch@1.0.20: {} - axe-core@4.12.1: {} + axe-core@4.13.0: {} axobject-query@4.1.0: {} @@ -13899,7 +14415,9 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.11.12: {} + baseline-browser-mapping@2.11.13: {} + + baseline-browser-mapping@2.11.14: {} bcp-47-match@2.0.3: {} @@ -13910,66 +14428,66 @@ snapshots: before-after-hook@3.0.2: {} - better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10): + better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9))(next@16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10): dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/drizzle-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9)) - '@better-auth/kysely-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4) - '@better-auth/memory-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/prisma-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/telemetry': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) + '@better-auth/drizzle-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9)) + '@better-auth/kysely-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.5) + '@better-auth/memory-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 - '@noble/ciphers': 2.2.0 - '@noble/hashes': 2.2.0 + '@noble/ciphers': 2.3.0 + '@noble/hashes': 2.3.0 better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 jose: 6.2.8 - kysely: 0.29.4 + kysely: 0.29.5 nanostores: 1.4.2 zod: 4.4.3 optionalDependencies: drizzle-kit: 1.0.0-rc.4 - drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9) + drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9) next: 16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - pg: 8.22.0 + pg: 8.23.0 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) solid-js: 1.9.14 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' - better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10): + better-auth@1.7.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(drizzle-kit@1.0.0-rc.4)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14)(vitest@4.1.10): dependencies: - '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) - '@better-auth/drizzle-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3)) - '@better-auth/kysely-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4) - '@better-auth/memory-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/prisma-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) - '@better-auth/telemetry': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/core': 1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2) + '@better-auth/drizzle-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3)) + '@better-auth/kysely-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.5) + '@better-auth/memory-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.7.0-rc.4(@better-auth/core@1.7.0-rc.4(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260702.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.5)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 - '@noble/ciphers': 2.2.0 - '@noble/hashes': 2.2.0 + '@noble/ciphers': 2.3.0 + '@noble/hashes': 2.3.0 better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 jose: 6.2.8 - kysely: 0.29.4 + kysely: 0.29.5 nanostores: 1.4.2 zod: 4.4.3 optionalDependencies: drizzle-kit: 1.0.0-rc.4 - drizzle-orm: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + drizzle-orm: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) next: 16.2.6(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - pg: 8.22.0 + pg: 8.23.0 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) solid-js: 1.9.14 - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -14026,17 +14544,17 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.28.7: + browserslist@4.28.8: dependencies: - baseline-browser-mapping: 2.11.12 - caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.401 - node-releases: 2.0.52 - update-browserslist-db: 1.2.3(browserslist@4.28.7) + baseline-browser-mapping: 2.11.13 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.405 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) buffer-image-size@0.6.4: dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 bundle-name@4.1.0: dependencies: @@ -14063,7 +14581,7 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001806: {} + caniuse-lite@1.0.30001809: {} ccount@2.0.1: {} @@ -14090,7 +14608,7 @@ snapshots: chokidar@5.0.0: dependencies: - readdirp: 5.0.0 + readdirp: 5.1.1 ci-info@4.4.0: {} @@ -14219,7 +14737,7 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 - core-js@3.49.0: {} + core-js@3.50.0: {} core-util-is@1.0.3: {} @@ -14301,7 +14819,7 @@ snapshots: dependencies: character-entities: 2.0.2 - decode-uri-component@0.4.1: {} + decode-uri-component@0.5.0: {} dedent@1.5.1: {} @@ -14387,35 +14905,48 @@ snapshots: '@drizzle-team/brocli': 0.12.0 '@js-temporal/polyfill': 0.5.1 esbuild: 0.25.12 - get-tsconfig: 4.14.1 + get-tsconfig: 4.14.2 jiti: 2.7.0 - drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(kysely@0.28.17)(pg@8.22.0)(postgres@3.4.9): + drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260702.1)(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(kysely@0.29.5)(pg@8.23.0)(postgres@3.4.9): optionalDependencies: '@cloudflare/workers-types': 4.20260702.1 '@electric-sql/pglite': 0.5.4 '@opentelemetry/api': 1.9.1 - '@types/pg': 8.20.4 - kysely: 0.28.17 - pg: 8.22.0 + '@types/pg': 8.21.0 + kysely: 0.29.5 + pg: 8.23.0 postgres: 3.4.9 - drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3): + drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3): optionalDependencies: '@cloudflare/workers-types': 4.20260702.1 - '@effect/sql-pg': 4.0.0-beta.103(effect@4.0.0-beta.103) - '@effect/sql-pglite': 4.0.0-beta.103(effect@4.0.0-beta.103) + '@effect/sql-pg': 4.0.0-beta.107(effect@4.0.0-beta.107) + '@effect/sql-pglite': 4.0.0-beta.107(effect@4.0.0-beta.107) '@electric-sql/pglite': 0.5.4 '@opentelemetry/api': 1.9.1 - '@types/pg': 8.20.4 - effect: 4.0.0-beta.103 - pg: 8.22.0 + '@types/pg': 8.21.0 + effect: 4.0.0-beta.107 + pg: 8.23.0 postgres: 3.4.9 zod: 4.4.3 - drizzle-seed@1.0.0-rc.4(drizzle-orm@1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3)): + drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3): + optionalDependencies: + '@cloudflare/workers-types': 4.20260702.1 + '@effect/sql-pg': 4.0.0-beta.107(effect@4.0.0-beta.107) + '@effect/sql-pglite': 4.0.0-beta.107(effect@4.0.0-beta.107) + '@electric-sql/pglite': 0.5.4 + '@opentelemetry/api': 1.9.1 + '@types/pg': 8.21.0 + effect: 4.0.0-beta.107 + pg: 8.23.0 + postgres: 3.4.9 + zod: 4.4.3 + + drizzle-seed@1.0.0-rc.4(drizzle-orm@1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3)): dependencies: - drizzle-orm: 1.0.0-rc.4(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.103(effect@4.0.0-beta.103))(@effect/sql-pglite@4.0.0-beta.103(effect@4.0.0-beta.103))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.20.4)(effect@4.0.0-beta.103)(pg@8.22.0)(postgres@3.4.9)(zod@4.4.3) + drizzle-orm: 1.0.0-rc.5-169397b(@cloudflare/workers-types@4.20260702.1)(@effect/sql-pg@4.0.0-beta.107(effect@4.0.0-beta.107))(@effect/sql-pglite@4.0.0-beta.107(effect@4.0.0-beta.107))(@electric-sql/pglite@0.5.4)(@opentelemetry/api@1.9.1)(@types/pg@8.21.0)(effect@4.0.0-beta.107)(pg@8.23.0)(postgres@3.4.9)(zod@4.4.3) pure-rand: 6.1.0 dset@3.1.4: {} @@ -14430,20 +14961,15 @@ snapshots: ee-first@1.1.1: {} - effect@4.0.0-beta.103: + effect@4.0.0-beta.107: dependencies: '@standard-schema/spec': 1.1.0 fast-check: 4.9.0 - find-my-way-ts: 0.1.6 - ini: 7.0.0 kubernetes-types: 1.30.0 msgpackr: 2.0.5 - multipasta: 0.2.8 - toml: 4.3.0 uuid: 14.0.1 - yaml: 2.9.0 - electron-to-chromium@1.5.401: {} + electron-to-chromium@1.5.405: {} elkjs@0.11.1: {} @@ -14467,7 +14993,7 @@ snapshots: engine.io@6.6.9: dependencies: '@types/cors': 2.8.19 - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 @@ -14475,7 +15001,7 @@ snapshots: cors: 2.8.6 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.21.2 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - supports-color @@ -14620,6 +15146,35 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -14659,8 +15214,6 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/unist': 3.0.3 - estree-walker@2.0.2: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -14669,11 +15222,11 @@ snapshots: eventemitter3@5.0.4: {} - eventsource-parser@3.1.0: {} + eventsource-parser@3.1.1: {} eventsource@3.0.7: dependencies: - eventsource-parser: 3.1.0 + eventsource-parser: 3.1.1 execa@5.1.1: dependencies: @@ -14708,7 +15261,7 @@ snapshots: dependencies: debug: 4.4.3 express: 5.2.1 - ip-address: 10.4.0 + ip-address: 10.5.0 transitivePeerDependencies: - supports-color @@ -14831,8 +15384,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-my-way-ts@0.1.6: {} - find-process@2.1.1: dependencies: chalk: 4.1.2 @@ -14928,7 +15479,7 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-tsconfig@4.14.1: + get-tsconfig@4.14.2: dependencies: resolve-pkg-maps: 1.0.0 @@ -14999,15 +15550,15 @@ snapshots: ufo: 1.6.4 uncrypto: 0.1.3 - happy-dom@20.11.1: + happy-dom@20.11.2: dependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 buffer-image-size: 0.6.4 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.21.2 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -15242,7 +15793,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.7 - hono@4.13.0: {} + hono@4.13.1: {} html-entities@2.3.3: {} @@ -15328,8 +15879,6 @@ snapshots: inherits@2.0.4: {} - ini@7.0.0: {} - inline-style-parser@0.2.7: {} ioredis@5.11.1: @@ -15344,7 +15893,7 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@10.4.0: {} + ip-address@10.5.0: {} ipaddr.js@1.9.1: {} @@ -15550,7 +16099,7 @@ snapshots: kysely@0.28.17: {} - kysely@0.29.4: {} + kysely@0.29.5: {} leac@0.6.0: {} @@ -15704,7 +16253,7 @@ snapshots: dependencies: yallist: 3.1.1 - lucide-react@1.28.0(react@19.2.8): + lucide-react@1.31.0(react@19.2.8): dependencies: react: 19.2.8 @@ -15712,7 +16261,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@1.1.0: + magic-string@1.1.1: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -16212,12 +16761,12 @@ snapshots: - bufferutil - utf-8-validate - miniflare@5.20260730.0-alpha: + miniflare@5.20260811.0-alpha: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.35.2 - undici: 7.28.0 - workerd: 1.20260730.1 + undici: 7.29.0 + workerd: 1.20260811.1 ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -16284,9 +16833,9 @@ snapshots: - '@types/node' optional: true - msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3): + msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3): dependencies: - '@inquirer/confirm': 6.1.1(@types/node@26.1.2) + '@inquirer/confirm': 6.1.1(@types/node@26.2.0) '@mswjs/interceptors': 0.41.9 '@open-draft/deferred-promise': 3.0.0 '@types/statuses': 2.0.6 @@ -16311,11 +16860,9 @@ snapshots: muggle-string@0.4.1: {} - multipasta@0.2.8: {} - mute-stream@3.0.0: {} - nanoid@3.3.17: {} + nanoid@3.3.18: {} nanoid@5.1.16: {} @@ -16335,8 +16882,8 @@ snapshots: dependencies: '@next/env': 16.2.3 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.11.12 - caniuse-lite: 1.0.30001806 + baseline-browser-mapping: 2.11.13 + caniuse-lite: 1.0.30001809 postcss: 8.4.31 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -16361,8 +16908,8 @@ snapshots: dependencies: '@next/env': 16.2.6 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.11.12 - caniuse-lite: 1.0.30001806 + baseline-browser-mapping: 2.11.14 + caniuse-lite: 1.0.30001809 postcss: 8.4.31 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) @@ -16409,7 +16956,7 @@ snapshots: node-mock-http@1.0.5: {} - node-releases@2.0.52: {} + node-releases@2.0.53: {} nodemailer@8.0.11: {} @@ -16624,19 +17171,19 @@ snapshots: pg-connection-string@2.14.0: {} - pg-cursor@2.21.0(pg@8.22.0): + pg-cursor@2.22.0(pg@8.23.0): dependencies: - pg: 8.22.0 + pg: 8.23.0 pg-int8@1.0.1: {} pg-numeric@1.0.2: {} - pg-pool@3.14.0(pg@8.22.0): + pg-pool@3.14.0(pg@8.23.0): dependencies: - pg: 8.22.0 + pg: 8.23.0 - pg-protocol@1.15.0: {} + pg-protocol@1.16.0: {} pg-types@2.2.0: dependencies: @@ -16656,11 +17203,11 @@ snapshots: postgres-interval: 3.0.0 postgres-range: 1.1.4 - pg@8.22.0: + pg@8.23.0: dependencies: pg-connection-string: 2.14.0 - pg-pool: 3.14.0(pg@8.22.0) - pg-protocol: 1.15.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -16701,20 +17248,20 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.4: + postcss-selector-parser@7.1.5: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss@8.4.31: dependencies: - nanoid: 3.3.17 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.25: + postcss@8.5.26: dependencies: - nanoid: 3.3.17 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -16742,12 +17289,12 @@ snapshots: postgres@3.4.9: {} - posthog-js@1.411.0: + posthog-js@1.416.0: dependencies: - '@posthog/browser-common': 0.3.1 - '@posthog/core': 1.46.7 - '@posthog/types': 1.401.0 - core-js: 3.49.0 + '@posthog/browser-common': 0.5.0 + '@posthog/core': 1.47.1 + '@posthog/types': 1.403.0 + core-js: 3.50.0 dompurify: 3.4.13 fflate: 0.4.9 preact: 10.29.8 @@ -16798,19 +17345,19 @@ snapshots: property-information@7.2.0: {} - prosekit@0.21.4(@shikijs/types@4.4.2)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14): + prosekit@0.21.4(@shikijs/types@4.4.3)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(solid-js@1.9.14): dependencies: - '@prosekit/basic': 0.9.5(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/basic': 0.9.5(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosekit/core': 0.12.3(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0) - '@prosekit/extensions': 0.17.4(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) - '@prosekit/lit': 0.6.7(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/extensions': 0.17.4(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/lit': 0.6.7(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) '@prosekit/pm': 0.1.18 - '@prosekit/preact': 0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) - '@prosekit/react': 0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@prosekit/solid': 0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(solid-js@1.9.14) - '@prosekit/svelte': 0.9.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) - '@prosekit/vue': 0.7.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) - '@prosekit/web': 0.8.6(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/preact': 0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(preact@10.29.8)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/react': 0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@prosekit/solid': 0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2)(solid-js@1.9.14) + '@prosekit/svelte': 0.9.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/vue': 0.7.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) + '@prosekit/web': 0.8.6(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2) optionalDependencies: preact: 10.29.8 react: 19.2.8 @@ -16834,7 +17381,7 @@ snapshots: dependencies: prosemirror-transform: 1.12.0 - prosemirror-commands@1.7.1: + prosemirror-commands@1.7.2: dependencies: prosemirror-model: 1.25.11 prosemirror-state: 1.4.4 @@ -16862,7 +17409,7 @@ snapshots: prosemirror-flat-list@0.6.0: dependencies: - prosemirror-commands: 1.7.1 + prosemirror-commands: 1.7.2 prosemirror-inputrules: 1.5.1 prosemirror-model: 1.25.11 prosemirror-safari-ime-span: 1.0.2 @@ -16877,9 +17424,9 @@ snapshots: prosemirror-state: 1.4.4 prosemirror-view: 1.42.2 - prosemirror-highlight@0.15.3(@shikijs/types@4.4.2)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2): + prosemirror-highlight@0.15.3(@shikijs/types@4.4.3)(@types/hast@3.0.5)(prosemirror-model@1.25.11)(prosemirror-state@1.4.4)(prosemirror-transform@1.12.0)(prosemirror-view@1.42.2): optionalDependencies: - '@shikijs/types': 4.4.2 + '@shikijs/types': 4.4.3 '@types/hast': 3.0.5 prosemirror-model: 1.25.11 prosemirror-state: 1.4.4 @@ -16973,9 +17520,9 @@ snapshots: query-selector-shadow-dom@1.0.1: {} - query-string@9.4.1: + query-string@9.5.0: dependencies: - decode-uri-component: 0.4.1 + decode-uri-component: 0.5.0 filter-obj: 5.1.0 split-on-first: 3.0.0 @@ -17010,7 +17557,7 @@ snapshots: react: 19.2.8 scheduler: 0.27.0 - react-email@6.9.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + react-email@6.9.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@babel/parser': 7.29.2 '@babel/traverse': 7.29.0 @@ -17020,7 +17567,7 @@ snapshots: conf: 15.1.0 css-tree: 3.2.1 debounce: 2.2.0 - esbuild: 0.28.1 + esbuild: 0.28.2 glob: 13.0.6 jiti: 2.6.1 log-symbols: 7.0.1 @@ -17070,9 +17617,9 @@ snapshots: readdirp@4.1.2: {} - readdirp@5.0.0: {} + readdirp@5.1.1: {} - recast@0.23.19: + recast@0.23.21: dependencies: ast-types: 0.16.1 esprima: 4.0.1 @@ -17266,25 +17813,25 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.16 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.16 - rolldown@1.2.2: + rolldown@1.2.4: dependencies: - '@oxc-project/types': 0.142.0 + '@oxc-project/types': 0.144.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.2 - '@rolldown/binding-darwin-arm64': 1.2.2 - '@rolldown/binding-darwin-x64': 1.2.2 - '@rolldown/binding-freebsd-x64': 1.2.2 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.2 - '@rolldown/binding-linux-arm64-gnu': 1.2.2 - '@rolldown/binding-linux-arm64-musl': 1.2.2 - '@rolldown/binding-linux-ppc64-gnu': 1.2.2 - '@rolldown/binding-linux-s390x-gnu': 1.2.2 - '@rolldown/binding-linux-x64-gnu': 1.2.2 - '@rolldown/binding-linux-x64-musl': 1.2.2 - '@rolldown/binding-openharmony-arm64': 1.2.2 - '@rolldown/binding-win32-arm64-msvc': 1.2.2 - '@rolldown/binding-win32-x64-msvc': 1.2.2 + '@rolldown/binding-android-arm64': 1.2.4 + '@rolldown/binding-darwin-arm64': 1.2.4 + '@rolldown/binding-darwin-x64': 1.2.4 + '@rolldown/binding-freebsd-x64': 1.2.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 + '@rolldown/binding-linux-arm64-gnu': 1.2.4 + '@rolldown/binding-linux-arm64-musl': 1.2.4 + '@rolldown/binding-linux-ppc64-gnu': 1.2.4 + '@rolldown/binding-linux-s390x-gnu': 1.2.4 + '@rolldown/binding-linux-x64-gnu': 1.2.4 + '@rolldown/binding-linux-x64-musl': 1.2.4 + '@rolldown/binding-openharmony-arm64': 1.2.4 + '@rolldown/binding-win32-arm64-msvc': 1.2.4 + '@rolldown/binding-win32-x64-msvc': 1.2.4 rollup@4.62.4: dependencies: @@ -17433,7 +17980,7 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@3.8.5(@types/node@26.1.2)(typescript@6.0.3): + shadcn@3.8.5(@types/node@26.2.0)(typescript@6.0.3): dependencies: '@antfu/ni': 25.0.0 '@babel/core': 7.29.7 @@ -17443,7 +17990,7 @@ snapshots: '@dotenvx/dotenvx': 1.75.1 '@modelcontextprotocol/sdk': 1.30.0(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 - browserslist: 4.28.7 + browserslist: 4.28.8 commander: 14.0.3 cosmiconfig: 9.0.2(typescript@6.0.3) dedent: 1.7.2 @@ -17455,14 +18002,14 @@ snapshots: fuzzysort: 3.1.0 https-proxy-agent: 7.0.6 kleur: 4.1.5 - msw: 2.15.0(@types/node@26.1.2)(typescript@6.0.3) + msw: 2.15.0(@types/node@26.2.0)(typescript@6.0.3) node-fetch: 3.3.2 open: 11.0.0 ora: 8.2.0 - postcss: 8.5.25 - postcss-selector-parser: 7.1.4 + postcss: 8.5.26 + postcss-selector-parser: 7.1.5 prompts: 2.4.2 - recast: 0.23.19 + recast: 0.23.21 stringify-object: 5.0.0 tailwind-merge: 3.6.0 ts-morph: 26.0.0 @@ -17541,7 +18088,7 @@ snapshots: '@img/sharp-win32-ia32': 0.35.2 '@img/sharp-win32-x64': 0.35.2 - sharp@0.35.3(@types/node@26.1.2): + sharp@0.35.3(@types/node@26.2.0): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 @@ -17572,7 +18119,7 @@ snapshots: '@img/sharp-win32-arm64': 0.35.3 '@img/sharp-win32-ia32': 0.35.3 '@img/sharp-win32-x64': 0.35.3 - '@types/node': 26.1.2 + '@types/node': 26.2.0 optional: true shebang-command@2.0.0: @@ -17581,14 +18128,14 @@ snapshots: shebang-regex@3.0.0: {} - shiki@4.4.2: + shiki@4.4.3: dependencies: - '@shikijs/core': 4.4.2 - '@shikijs/engine-javascript': 4.4.2 - '@shikijs/engine-oniguruma': 4.4.2 - '@shikijs/langs': 4.4.2 - '@shikijs/themes': 4.4.2 - '@shikijs/types': 4.4.2 + '@shikijs/core': 4.4.3 + '@shikijs/engine-javascript': 4.4.3 + '@shikijs/engine-oniguruma': 4.4.3 + '@shikijs/langs': 4.4.3 + '@shikijs/themes': 4.4.3 + '@shikijs/types': 4.4.3 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 @@ -17638,12 +18185,12 @@ snapshots: slugify@1.6.9: {} - smol-toml@1.7.1: {} + smol-toml@1.8.0: {} socket.io-adapter@2.5.8: dependencies: debug: 4.4.3 - ws: 8.21.2 + ws: 8.21.3 transitivePeerDependencies: - bufferutil - supports-color @@ -17767,7 +18314,7 @@ snapshots: strip-ansi@7.2.0: dependencies: - ansi-regex: 6.2.2 + ansi-regex: 6.3.0 strip-bom@3.0.0: {} @@ -17824,11 +18371,11 @@ snapshots: tailwindcss@4.3.3: {} - takumi-js@2.5.7(csstype@3.2.3)(preact@10.29.8)(react@19.2.8): + takumi-js@2.7.2(csstype@3.2.3)(preact@10.29.8)(react@19.2.8): dependencies: - '@takumi-rs/core': 2.5.7(csstype@3.2.3)(preact@10.29.8)(react@19.2.8) - '@takumi-rs/helpers': 2.5.7(preact@10.29.8)(react@19.2.8) - '@takumi-rs/wasm': 2.5.7(csstype@3.2.3)(preact@10.29.8)(react@19.2.8) + '@takumi-rs/core': 2.7.2(csstype@3.2.3)(preact@10.29.8)(react@19.2.8) + '@takumi-rs/helpers': 2.7.2(preact@10.29.8)(react@19.2.8) + '@takumi-rs/wasm': 2.7.2(csstype@3.2.3)(preact@10.29.8)(react@19.2.8) transitivePeerDependencies: - csstype - preact @@ -17867,8 +18414,6 @@ snapshots: toidentifier@1.0.1: {} - toml@4.3.0: {} - totalist@3.0.1: {} tough-cookie@6.0.2: @@ -17900,20 +18445,20 @@ snapshots: tsscmp@1.0.6: {} - tsx@4.23.6: + tsx@4.23.12: dependencies: - esbuild: 0.28.1 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 - turbo@2.10.8: + turbo@2.10.9: optionalDependencies: - '@turbo/darwin-64': 2.10.8 - '@turbo/darwin-arm64': 2.10.8 - '@turbo/linux-64': 2.10.8 - '@turbo/linux-arm64': 2.10.8 - '@turbo/windows-64': 2.10.8 - '@turbo/windows-arm64': 2.10.8 + '@turbo/darwin-64': 2.10.9 + '@turbo/darwin-arm64': 2.10.9 + '@turbo/linux-64': 2.10.9 + '@turbo/linux-arm64': 2.10.9 + '@turbo/windows-64': 2.10.9 + '@turbo/windows-arm64': 2.10.9 tw-animate-css@1.4.0: {} @@ -17993,11 +18538,11 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unifont@0.7.4: + unifont@0.7.5: dependencies: css-tree: 3.2.1 - ofetch: 1.5.1 ohash: 2.0.11 + undici: 8.10.0 unist-util-find-after@5.0.0: dependencies: @@ -18044,16 +18589,16 @@ snapshots: picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.2)(rollup@4.62.4)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)): + unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 optionalDependencies: - esbuild: 0.28.1 - rolldown: 1.2.2 + esbuild: 0.28.2 + rolldown: 1.2.4 rollup: 4.62.4 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) unstorage@1.17.5(aws4fetch@1.0.20)(ioredis@5.11.1): dependencies: @@ -18071,9 +18616,9 @@ snapshots: until-async@3.0.2: {} - update-browserslist-db@1.2.3(browserslist@4.28.7): + update-browserslist-db@1.3.1(browserslist@4.28.8): dependencies: - browserslist: 4.28.7 + browserslist: 4.28.8 escalade: 3.2.0 picocolors: 1.1.1 @@ -18106,15 +18651,15 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-icons-spritesheet@3.1.0(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)): + vite-plugin-icons-spritesheet@3.1.0(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: chalk: 5.6.2 glob: 11.1.0 node-html-parser: 7.1.0 tinyexec: 0.3.2 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) - vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)): + vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@types/babel__core': 7.20.5 @@ -18122,85 +18667,85 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.14 solid-refresh: 0.6.3(solid-js@1.9.14) - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) - vitefu: 1.1.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) + vitefu: 1.1.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - vite-tsconfig-paths@5.1.4(typescript@6.0.3)(vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0)): + vite-tsconfig-paths@5.1.4(typescript@6.0.3)(vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.3) optionalDependencies: - vite: 6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - vite@6.4.3(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.6)(yaml@2.9.0): + vite@6.4.3(@types/node@26.2.0)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 - postcss: 8.5.25 + postcss: 8.5.26 rollup: 4.62.4 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.1.2 + '@types/node': 26.2.0 fsevents: 2.3.3 jiti: 2.7.0 lightningcss: 1.33.0 - tsx: 4.23.6 + tsx: 4.23.12 yaml: 2.9.0 - vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0): + vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.2 + postcss: 8.5.26 + rolldown: 1.2.4 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.13.3 - esbuild: 0.28.1 + esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 - tsx: 4.23.6 + tsx: 4.23.12 yaml: 2.9.0 - vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0): + vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.2 + postcss: 8.5.26 + rolldown: 1.2.4 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.1.2 - esbuild: 0.28.1 + '@types/node': 26.2.0 + esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 - tsx: 4.23.6 + tsx: 4.23.12 yaml: 2.9.0 - vitefu@1.1.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)): + vitefu@1.1.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): optionalDependencies: - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) vitest-browser-react@2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10): dependencies: react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) optionalDependencies: '@types/react': 19.2.18 '@types/react-dom': 19.2.4(@types/react@19.2.18) - vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -18217,20 +18762,20 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 24.13.3 - '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.0(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10) - happy-dom: 20.11.1 + '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@24.13.3)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) + happy-dom: 20.11.2 transitivePeerDependencies: - msw - vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.1)(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.2.0)(@vitest/browser-playwright@4.1.10)(happy-dom@20.11.2)(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -18247,13 +18792,13 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0) + vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 26.1.2 - '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@26.1.2)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.6)(yaml@2.9.0))(vitest@4.1.10) - happy-dom: 20.11.1 + '@types/node': 26.2.0 + '@vitest/browser-playwright': 4.1.10(msw@2.15.0(@types/node@26.2.0)(typescript@6.0.3))(playwright@1.62.1)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) + happy-dom: 20.11.2 transitivePeerDependencies: - msw @@ -18404,16 +18949,24 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260730.1 '@cloudflare/workerd-windows-64': 1.20260730.1 - wrangler@4.118.0(@cloudflare/workers-types@4.20260702.1): + workerd@1.20260811.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260811.1 + '@cloudflare/workerd-darwin-arm64': 1.20260811.1 + '@cloudflare/workerd-linux-64': 1.20260811.1 + '@cloudflare/workerd-linux-arm64': 1.20260811.1 + '@cloudflare/workerd-windows-64': 1.20260811.1 + + wrangler@4.122.0(@cloudflare/workers-types@4.20260702.1): dependencies: '@cloudflare/kv-asset-handler': 0.5.0 - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260730.1) + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260811.1) blake3-wasm: 2.1.5 esbuild: 0.28.1 - miniflare: 5.20260730.0-alpha + miniflare: 5.20260811.0-alpha path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260730.1 + workerd: 1.20260811.1 optionalDependencies: '@cloudflare/workers-types': 4.20260702.1 fsevents: 2.3.3 @@ -18443,7 +18996,7 @@ snapshots: ws@8.21.0: {} - ws@8.21.2: {} + ws@8.21.3: {} wsl-utils@0.1.0: dependencies: @@ -18525,7 +19078,7 @@ snapshots: dependencies: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.23 + '@speed-highlight/core': 1.2.24 cookie: 1.1.1 youch-core: 0.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 78ce95ca..80a0ec79 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -17,11 +17,14 @@ catalog: "@better-auth/utils": ^0.4.0 "@cloudflare/workers-types": ^4.20260702.1 "@dotenvx/dotenvx": ^1.75.1 - "@effect-aws/client-s3": ^2.0.0-beta.4 - "@effect-aws/s3": ^1.0.0-beta.4 - "@effect/platform-node": ^4.0.0-beta.66 - "@effect/sql-pg": ^4.0.0-beta.66 - "@effect/sql-pglite": ^4.0.0-beta.66 + "@effect-aws/client-s3": 2.0.0-beta.6 + "@effect-aws/s3": 1.0.0-beta.5 + "@effect/ai-openai": 4.0.0-beta.107 + "@effect/atom-react": 4.0.0-beta.107 + "@effect/platform-node": 4.0.0-beta.107 + "@effect/sql-pg": 4.0.0-beta.107 + "@effect/sql-pglite": 4.0.0-beta.107 + "@effect/vitest": 4.0.0-beta.107 "@floating-ui/dom": ^1.8.0 "@polar-sh/better-auth": ^1.8.3 "@polar-sh/sdk": ^0.47.1 @@ -42,9 +45,9 @@ catalog: clsx: ^2.1.1 dotenv: ^17.2.2 drizzle-kit: 1.0.0-rc.4 - drizzle-orm: 1.0.0-rc.4 + drizzle-orm: 1.0.0-rc.5-169397b drizzle-seed: 1.0.0-rc.4 - effect: ^4.0.0-beta.66 + effect: 4.0.0-beta.107 jose: ^6.2.3 postgres: ^3.4.7 react: ^19.2.8 @@ -65,4 +68,5 @@ catalog: nodeLinker: hoisted overrides: prosemirror-model: ^1.25.9 + "@effect/platform-node-shared": 4.0.0-beta.107