Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Refactor integration hooks type #11304

Merged
merged 6 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
florian-lefebvre marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -3017,83 +3017,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 {
florian-lefebvre marked this conversation as resolved.
Show resolved Hide resolved
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;
florian-lefebvre marked this conversation as resolved.
Show resolved Hide resolved
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 {
florian-lefebvre marked this conversation as resolved.
Show resolved Hide resolved
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
Loading