Skip to content

Commit

Permalink
feat: Refactor integration hooks type (#11304)
Browse files Browse the repository at this point in the history
* 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 <contact@florian-lefebvre.dev>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
3 people authored Jul 17, 2024
1 parent 683417f commit 2e70741
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 91 deletions.
23 changes: 23 additions & 0 deletions .changeset/blue-colts-film.md
Original file line number Diff line number Diff line change
@@ -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: {},
});
}
```
58 changes: 58 additions & 0 deletions .changeset/curvy-otters-jog.md
Original file line number Diff line number Diff line change
@@ -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<void>;
}
}
}
```

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) => {
// ...
},
}
}
}
```
153 changes: 81 additions & 72 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3056,83 +3056,92 @@ export type HookParameters<
Fn = AstroIntegration['hooks'][Hook],
> = Fn extends (...args: any) => any ? Parameters<Fn>[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>) => 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<void>;
'astro:config:done': (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:setup': (options: {
server: vite.ViteDevServer;
logger: AstroIntegrationLogger;
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
}) => void | Promise<void>;
'astro:server:start': (options: {
address: AddressInfo;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:done': (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'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<RouteData, URL>;
/**
* File path of the emitted middleware
*/
middlewareEntryPoint: URL | undefined;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:start': (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'astro:build:setup': (options: {
vite: vite.InlineConfig;
pages: Map<string, PageBuildData>;
target: 'client' | 'server';
updateConfig: (newConfig: vite.InlineConfig) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:generated': (options: {
dir: URL;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:done': (options: {
pages: { pathname: string }[];
dir: URL;
routes: RouteData[];
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
}
}
}

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>) => 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<void>;
'astro:config:done'?: (options: {
config: AstroConfig;
setAdapter: (adapter: AstroAdapter) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:setup'?: (options: {
server: vite.ViteDevServer;
logger: AstroIntegrationLogger;
toolbar: ReturnType<typeof getToolbarServerCommunicationHelpers>;
}) => void | Promise<void>;
'astro:server:start'?: (options: {
address: AddressInfo;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:server:done'?: (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'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<RouteData, URL>;
/**
* File path of the emitted middleware
*/
middlewareEntryPoint: URL | undefined;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:start'?: (options: { logger: AstroIntegrationLogger }) => void | Promise<void>;
'astro:build:setup'?: (options: {
vite: vite.InlineConfig;
pages: Map<string, PageBuildData>;
target: 'client' | 'server';
updateConfig: (newConfig: vite.InlineConfig) => void;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:generated'?: (options: {
dir: URL;
logger: AstroIntegrationLogger;
}) => void | Promise<void>;
'astro:build:done'?: (options: {
pages: { pathname: string }[];
dir: URL;
routes: RouteData[];
logger: AstroIntegrationLogger;
cacheManifest: boolean;
}) => void | Promise<void>;
};
[K in keyof Astro.IntegrationHooks]?: Astro.IntegrationHooks[K]
} & Partial<Record<string, unknown>>
}

export type RewritePayload = string | URL | Request;
Expand Down
8 changes: 2 additions & 6 deletions packages/db/src/core/load-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -31,7 +28,6 @@ export async function resolveDbConfig({
const integrationDbConfigPaths: Array<{ name: string; configEntrypoint: string | URL }> = [];
const integrationSeedPaths: Array<string | URL> = [];
for (const integration of integrations) {
if (!isDbIntegration(integration)) continue;
const { name, hooks } = integration;
if (hooks['astro:db:setup']) {
hooks['astro:db:setup']({
Expand Down
24 changes: 13 additions & 11 deletions packages/db/src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { AstroIntegration } from 'astro';
import type { z } from 'zod';
import type {
MaybeArray,
Expand Down Expand Up @@ -88,13 +87,16 @@ interface LegacyIndexConfig<TColumns extends ColumnsConfig>
export type NumberColumnOpts = z.input<typeof numberColumnOptsSchema>;
export type TextColumnOpts = z.input<typeof textColumnOptsSchema>;

export type AstroDbIntegration = AstroIntegration & {
hooks: {
'astro:db:setup'?: (options: {
extendDb: (options: {
configEntrypoint?: URL | string;
seedEntrypoint?: URL | string;
}) => void;
}) => void | Promise<void>;
};
};
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<void>;
}
}
}
4 changes: 2 additions & 2 deletions packages/db/src/core/utils.ts
Original file line number Diff line number Diff line change
@@ -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<AstroConfig['vite']>['plugins'][number];

Expand All @@ -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;
}

Expand Down

0 comments on commit 2e70741

Please sign in to comment.