Skip to content

Commit

Permalink
feat: add typegen for loaders
Browse files Browse the repository at this point in the history
  • Loading branch information
ascorbic committed Jun 27, 2024
1 parent 8d310af commit 5b8fad4
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 17 deletions.
3 changes: 2 additions & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@
"which-pm": "^2.2.0",
"yargs-parser": "^21.1.1",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.1"
"zod-to-json-schema": "^3.23.1",
"zod-to-ts": "^1.2.0"
},
"optionalDependencies": {
"sharp": "^0.33.3"
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/content/loaders.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ZodSchema } from 'zod';
import type { AnyZodObject } from 'astro/zod';
import type { AstroSettings } from '../@types/astro.js';
import type { AstroIntegrationLogger, Logger } from '../core/logger/core.js';
import { DataStore, globalDataStore, type MetaStore, type ScopedDataStore } from './data-store.js';
Expand Down Expand Up @@ -30,13 +30,13 @@ export interface LoaderContext {
): T;
}

export interface Loader<S extends ZodSchema = ZodSchema> {
export interface Loader {
/** Unique name of the loader, e.g. the npm package name */
name: string;
/** Do the actual loading of the data */
load: (context: LoaderContext) => Promise<void>;
/** Optionally, define the schema of the data. Will be overridden by user-defined schema */
schema?: S | Promise<S> | (() => S | Promise<S>);
schema?: AnyZodObject | Promise<AnyZodObject> | (() => AnyZodObject | Promise<AnyZodObject>);
render?: (entry: any) => any;
}
export async function syncDataLayer({
Expand Down
28 changes: 23 additions & 5 deletions packages/astro/src/content/types-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import glob from 'fast-glob';
import { bold, cyan } from 'kleur/colors';
import { type ViteDevServer, normalizePath } from 'vite';
import { z } from 'zod';
import { zodToTs, printNode } from 'zod-to-ts';
import { zodToJsonSchema } from 'zod-to-json-schema';
import type { AstroSettings, ContentEntryType } from '../@types/astro.js';
import { AstroError } from '../core/errors/errors.js';
Expand Down Expand Up @@ -360,6 +361,27 @@ function normalizeConfigPath(from: string, to: string) {
return `"${isRelativePath(configPath) ? '' : './'}${normalizedPath}"` as const;
}

async function typeForCollection<T extends keyof ContentConfig['collections']>(
collection: ContentConfig['collections'][T] | undefined,
collectionKey: T
): Promise<string> {
if (collection?.schema) {
return `InferEntrySchema<${collectionKey}>`;
}

if (collection?.type === 'experimental_data' && collection.loader.schema) {
let schema = collection.loader.schema;
if (typeof schema === 'function') {
schema = await schema();
}
if (schema) {
const ast = zodToTs(schema);
return printNode(ast.node);
}
}
return 'any';
}

async function writeContentFiles({
fs,
contentPaths,
Expand Down Expand Up @@ -435,11 +457,7 @@ async function writeContentFiles({
: collection.type;

const collectionEntryKeys = Object.keys(collection.entries).sort();
const dataType =
collectionConfig?.schema ||
(collectionConfig?.type === 'experimental_data' && collectionConfig.loader?.schema)
? `InferEntrySchema<${collectionKey}>`
: 'any';
const dataType = await typeForCollection(collectionConfig, collectionKey);
switch (resolvedType) {
case 'content':
if (collectionEntryKeys.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Loader } from 'astro:content';
import { type Loader, z } from 'astro:content';

export interface PostLoaderConfig {
url: string;
Expand Down Expand Up @@ -30,5 +30,15 @@ export function loader(config:PostLoaderConfig): Loader {
}
meta.set('lastSynced', String(Date.now()));
},
schema: async () => {
// Simulate a delay
await new Promise((resolve) => setTimeout(resolve, 1000));
return z.object({
title: z.string(),
body: z.string(),
userId: z.number(),
id: z.number(),
});
}
};
}
3 changes: 2 additions & 1 deletion packages/astro/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"declarationDir": "./dist",
"outDir": "./dist",
"jsx": "preserve",
"types": ["@types/dom-view-transitions"]
"types": ["@types/dom-view-transitions"],
"rootDir": "src"
}
}
6 changes: 3 additions & 3 deletions packages/astro/types/content.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ declare module 'astro:content' {
props: ParseDataOptions
): T;
}
export interface Loader<S extends BaseSchema = BaseSchema> {
export interface Loader {
/** Unique name of the loader, e.g. the npm package name */
name: string;
/** Do the actual loading of the data */
load: (context: LoaderContext) => Promise<void>;
/** Optionally, define the schema of the data. Will be overridden by user-defined schema */
schema?: S | Promise<S> | (() => S | Promise<S>);
schema?: BaseSchema | Promise<BaseSchema> | (() => BaseSchema | Promise<BaseSchema>);
render?: (entry: any) => any;
}

Expand All @@ -82,7 +82,7 @@ declare module 'astro:content' {
type ContentCollectionV2Config<S extends BaseSchema> = {
type: 'experimental_data';
schema?: S | ((context: SchemaContext) => S);
loader: Loader<S>;
loader: Loader;
};

type DataCollectionConfig<S extends BaseSchema> = {
Expand Down
17 changes: 14 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5b8fad4

Please sign in to comment.