From b14c2e39522360dec9b5cd8da7979dbee3932c38 Mon Sep 17 00:00:00 2001 From: Luiz Ferraz Date: Wed, 17 Jul 2024 12:58:57 -0300 Subject: [PATCH] feat: Refactor integration hooks type (#11304) * feat: Refactor integration hooks type * Revert formatting changes * More detailed changelog * Add changeset for Astro DB * Apply suggestions from code review Co-authored-by: Florian Lefebvre Co-authored-by: Sarah Rainsberger --------- Co-authored-by: Florian Lefebvre Co-authored-by: Sarah Rainsberger --- .changeset/blue-colts-film.md | 23 +++++ .changeset/curvy-otters-jog.md | 58 +++++++++++ packages/astro/src/@types/astro.ts | 153 +++++++++++++++-------------- packages/db/src/core/load-file.ts | 8 +- packages/db/src/core/types.ts | 24 ++--- packages/db/src/core/utils.ts | 4 +- 6 files changed, 179 insertions(+), 91 deletions(-) create mode 100644 .changeset/blue-colts-film.md create mode 100644 .changeset/curvy-otters-jog.md diff --git a/.changeset/blue-colts-film.md b/.changeset/blue-colts-film.md new file mode 100644 index 0000000000..9df2e9e560 --- /dev/null +++ b/.changeset/blue-colts-film.md @@ -0,0 +1,23 @@ +--- +'@astrojs/db': minor +--- + +Removes the `AstroDbIntegration` type + +Astro integration hooks can now be extended and as such `@astrojs/db` no longer needs to declare it's own integration type. Using `AstroIntegration` will have the same type. + +If you were using the `AstroDbIntegration` type, apply this change to your integration code: + +```diff +- import { defineDbIntegration, type AstroDbIntegration } from '@astrojs/db/utils'; ++ import { defineDbIntegration } from '@astrojs/db/utils'; +import type { AstroIntegration } from 'astro'; + +- export default (): AstroDbIntegration => { ++ export default (): AstroIntegration => { + return defineDbIntegration({ + name: 'your-integration', + hooks: {}, + }); +} +``` diff --git a/.changeset/curvy-otters-jog.md b/.changeset/curvy-otters-jog.md new file mode 100644 index 0000000000..8bfa0a17ce --- /dev/null +++ b/.changeset/curvy-otters-jog.md @@ -0,0 +1,58 @@ +--- +'astro': minor +--- + +Refactors the type for integration hooks so that integration authors writing custom integration hooks can now allow runtime interactions between their integration and other integrations. + +This internal change should not break existing code for integration authors. + +To declare your own hooks for your integration, extend the `Astro.IntegrationHooks` interface: + +```ts +// your-integration/types.ts +declare global { + namespace Astro { + interface IntegrationHooks { + 'myLib:eventHappened': (your: string, parameters: number) => Promise; + } + } +} +``` + +Call your hooks on all other integrations installed in a project at the appropriate time. For example, you can call your hook on initialization before either the Vite or Astro config have resolved: + +```ts +// your-integration/index.ts +import './types.ts'; + +export default (): AstroIntegration => { + return { + name: 'your-integration', + hooks: { + 'astro:config:setup': async ({ config }) => { + for (const integration of config.integrations) { + await integration.hooks['myLib:eventHappened'].?('your values', 123); + } + }, + } + } +} +``` + +Other integrations can also now declare your hooks: + +```ts +// other-integration/index.ts +import 'your-integration/types.ts'; + +export default (): AstroIntegration => { + return { + name: 'other-integration', + hooks: { + 'myLib:eventHappened': async (your, values) => { + // ... + }, + } + } +} +``` diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index aac7b4dcb0..56e548d020 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -3056,83 +3056,92 @@ export type HookParameters< Fn = AstroIntegration['hooks'][Hook], > = Fn extends (...args: any) => any ? Parameters[0] : never; +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Astro { + export interface IntegrationHooks { + 'astro:config:setup': (options: { + config: AstroConfig; + command: 'dev' | 'build' | 'preview'; + isRestart: boolean; + updateConfig: (newConfig: DeepPartial) => AstroConfig; + addRenderer: (renderer: AstroRenderer) => void; + addWatchFile: (path: URL | string) => void; + injectScript: (stage: InjectedScriptStage, content: string) => void; + injectRoute: (injectRoute: InjectedRoute) => void; + addClientDirective: (directive: ClientDirectiveConfig) => void; + /** + * @deprecated Use `addDevToolbarApp` instead. + * TODO: Fully remove in Astro 5.0 + */ + addDevOverlayPlugin: (entrypoint: string) => void; + // TODO: Deprecate the `string` overload once a few apps have been migrated to the new API. + addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void; + addMiddleware: (mid: AstroIntegrationMiddleware) => void; + logger: AstroIntegrationLogger; + // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. + // This may require some refactoring of `scripts`, `styles`, and `links` into something + // more generalized. Consider the SSR use-case as well. + // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; + }) => void | Promise; + 'astro:config:done': (options: { + config: AstroConfig; + setAdapter: (adapter: AstroAdapter) => void; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:setup': (options: { + server: vite.ViteDevServer; + logger: AstroIntegrationLogger; + toolbar: ReturnType; + }) => void | Promise; + 'astro:server:start': (options: { + address: AddressInfo; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:done': (options: { logger: AstroIntegrationLogger }) => void | Promise; + 'astro:build:ssr': (options: { + manifest: SerializedSSRManifest; + /** + * This maps a {@link RouteData} to an {@link URL}, this URL represents + * the physical file you should import. + */ + entryPoints: Map; + /** + * File path of the emitted middleware + */ + middlewareEntryPoint: URL | undefined; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:start': (options: { logger: AstroIntegrationLogger }) => void | Promise; + 'astro:build:setup': (options: { + vite: vite.InlineConfig; + pages: Map; + target: 'client' | 'server'; + updateConfig: (newConfig: vite.InlineConfig) => void; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:generated': (options: { + dir: URL; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:done': (options: { + pages: { pathname: string }[]; + dir: URL; + routes: RouteData[]; + logger: AstroIntegrationLogger; + cacheManifest: boolean; + }) => void | Promise; + } + } +} + export interface AstroIntegration { /** The name of the integration. */ name: string; /** The different hooks available to extend. */ hooks: { - 'astro:config:setup'?: (options: { - config: AstroConfig; - command: 'dev' | 'build' | 'preview'; - isRestart: boolean; - updateConfig: (newConfig: DeepPartial) => AstroConfig; - addRenderer: (renderer: AstroRenderer) => void; - addWatchFile: (path: URL | string) => void; - injectScript: (stage: InjectedScriptStage, content: string) => void; - injectRoute: (injectRoute: InjectedRoute) => void; - addClientDirective: (directive: ClientDirectiveConfig) => void; - /** - * @deprecated Use `addDevToolbarApp` instead. - * TODO: Fully remove in Astro 5.0 - */ - addDevOverlayPlugin: (entrypoint: string) => void; - // TODO: Deprecate the `string` overload once a few apps have been migrated to the new API. - addDevToolbarApp: (entrypoint: DevToolbarAppEntry | string) => void; - addMiddleware: (mid: AstroIntegrationMiddleware) => void; - logger: AstroIntegrationLogger; - // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. - // This may require some refactoring of `scripts`, `styles`, and `links` into something - // more generalized. Consider the SSR use-case as well. - // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; - }) => void | Promise; - 'astro:config:done'?: (options: { - config: AstroConfig; - setAdapter: (adapter: AstroAdapter) => void; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:server:setup'?: (options: { - server: vite.ViteDevServer; - logger: AstroIntegrationLogger; - toolbar: ReturnType; - }) => void | Promise; - 'astro:server:start'?: (options: { - address: AddressInfo; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:server:done'?: (options: { logger: AstroIntegrationLogger }) => void | Promise; - 'astro:build:ssr'?: (options: { - manifest: SerializedSSRManifest; - /** - * This maps a {@link RouteData} to an {@link URL}, this URL represents - * the physical file you should import. - */ - entryPoints: Map; - /** - * File path of the emitted middleware - */ - middlewareEntryPoint: URL | undefined; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:build:start'?: (options: { logger: AstroIntegrationLogger }) => void | Promise; - 'astro:build:setup'?: (options: { - vite: vite.InlineConfig; - pages: Map; - target: 'client' | 'server'; - updateConfig: (newConfig: vite.InlineConfig) => void; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:build:generated'?: (options: { - dir: URL; - logger: AstroIntegrationLogger; - }) => void | Promise; - 'astro:build:done'?: (options: { - pages: { pathname: string }[]; - dir: URL; - routes: RouteData[]; - logger: AstroIntegrationLogger; - cacheManifest: boolean; - }) => void | Promise; - }; + [K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K] + } & Partial> } export type RewritePayload = string | URL | Request; diff --git a/packages/db/src/core/load-file.ts b/packages/db/src/core/load-file.ts index dd48df928c..cbdb6d2436 100644 --- a/packages/db/src/core/load-file.ts +++ b/packages/db/src/core/load-file.ts @@ -2,19 +2,16 @@ import { existsSync } from 'node:fs'; import { unlink, writeFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import type { AstroConfig, AstroIntegration } from 'astro'; +import type { AstroConfig } from 'astro'; import { build as esbuild } from 'esbuild'; import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js'; import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js'; import { errorMap } from './integration/error-map.js'; import { getConfigVirtualModContents } from './integration/vite-plugin-db.js'; import { dbConfigSchema } from './schemas.js'; -import { type AstroDbIntegration } from './types.js'; +import './types.js'; import { getAstroEnv, getDbDirectoryUrl } from './utils.js'; -const isDbIntegration = (integration: AstroIntegration): integration is AstroDbIntegration => - 'astro:db:setup' in integration.hooks; - /** * Load a user’s `astro:db` configuration file and additional configuration files provided by integrations. */ @@ -31,7 +28,6 @@ export async function resolveDbConfig({ const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = []; const integrationSeedPaths: Array = []; for (const integration of integrations) { - if (!isDbIntegration(integration)) continue; const { name, hooks } = integration; if (hooks['astro:db:setup']) { hooks['astro:db:setup']({ diff --git a/packages/db/src/core/types.ts b/packages/db/src/core/types.ts index 79bbdf3719..5efc6507c8 100644 --- a/packages/db/src/core/types.ts +++ b/packages/db/src/core/types.ts @@ -1,4 +1,3 @@ -import type { AstroIntegration } from 'astro'; import type { z } from 'zod'; import type { MaybeArray, @@ -88,13 +87,16 @@ interface LegacyIndexConfig export type NumberColumnOpts = z.input; export type TextColumnOpts = z.input; -export type AstroDbIntegration = AstroIntegration & { - hooks: { - 'astro:db:setup'?: (options: { - extendDb: (options: { - configEntrypoint?: URL | string; - seedEntrypoint?: URL | string; - }) => void; - }) => void | Promise; - }; -}; +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Astro { + export interface IntegrationHooks { + 'astro:db:setup'?: (options: { + extendDb: (options: { + configEntrypoint?: URL | string; + seedEntrypoint?: URL | string; + }) => void; + }) => void | Promise; + } + } +} diff --git a/packages/db/src/core/utils.ts b/packages/db/src/core/utils.ts index 4fef5fbe18..c1cf565794 100644 --- a/packages/db/src/core/utils.ts +++ b/packages/db/src/core/utils.ts @@ -1,7 +1,7 @@ import { getAstroStudioEnv } from '@astrojs/studio'; import type { AstroConfig, AstroIntegration } from 'astro'; import { loadEnv } from 'vite'; -import type { AstroDbIntegration } from './types.js'; +import './types.js'; export type VitePlugin = Required['plugins'][number]; @@ -19,7 +19,7 @@ export function getDbDirectoryUrl(root: URL | string) { return new URL('db/', root); } -export function defineDbIntegration(integration: AstroDbIntegration): AstroIntegration { +export function defineDbIntegration(integration: AstroIntegration): AstroIntegration { return integration; }