Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
6 changes: 3 additions & 3 deletions demos/loaders/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
"@ascorbic/youtube-loader": "workspace:^",
"@astro-community/astro-embed-bluesky": "^0.1.3",
"@astro-community/astro-embed-youtube": "^0.5.6",
"@astrojs/check": "^0.9.4",
"@astrojs/netlify": "^6.4.0",
"@astrojs/check": "^0.9.7",
"@astrojs/netlify": "^7.0.2",
"@atproto/api": "^0.13.31",
"@unpic/astro": "^1.0.0",
"astro": "^5.10.1",
"astro": "^6.0.4",
"typescript": "^5.7.3"
}
}
7 changes: 4 additions & 3 deletions packages/airtable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"astro": "^5.10.1",
"astro": "^6.0.8",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [
"withastro",
Expand All @@ -43,6 +43,7 @@
"dependencies": {
"@ascorbic/loader-utils": "workspace:^",
"airtable": "^0.12.2",
"ts-pattern": "^5.6.2"
"ts-pattern": "^5.6.2",
"zod-to-ts": "^1.2.0"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zod-to-ts v2.0.0 is available, but the API is different from what the Astro docs expect. Since Astro v5 used zod-to-ts@1.2.0, I kept the version there for parity.

}
}
15 changes: 12 additions & 3 deletions packages/airtable/src/airtable-loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Loader } from "astro/loaders";
import { AstroError } from "astro/errors";
import Airtable, { type Query, type FieldSet } from "airtable";
import { createTypeAlias, printNode, zodToTs } from "zod-to-ts";
import { zodSchemaFromAirtableTable } from "./schema.js";

export interface AirtableLoaderOptions<TFields extends FieldSet = FieldSet> {
Expand Down Expand Up @@ -44,11 +45,19 @@ export function airtableLoader({
}
logger.info(`Loaded ${records.length} records from "${table}"`);
},
schema: () =>
zodSchemaFromAirtableTable({
createSchema: async () => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the schema generation is async we need to use createSchema now: https://docs.astro.build/en/guides/upgrade-to/v6/#removed-schema-function-signature-content-loader-api

const schema = await zodSchemaFromAirtableTable({
baseID: base,
tableIdOrName: table,
apiKey: token,
}),
});
const identifier = "Entry";
const { node } = zodToTs(schema, identifier);
const typeAlias = createTypeAlias(node, identifier);
return {
schema,
types: `export ${printNode(typeAlias)}`,
};
},
};
}
12 changes: 6 additions & 6 deletions packages/airtable/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ const BOOLEAN_TYPES = new Set(["checkbox", "boolean"]);
// Define schemas for complex field types
const userSchema = z.object({
id: z.string(),
email: z.string().email(),
email: z.email(),
name: z.string(),
});

const attachmentSchema = z.object({
id: z.string(),
url: z.string().url(),
url: z.url(),
filename: z.string(),
size: z.number().optional(),
type: z.string().optional(),
Expand All @@ -78,8 +78,8 @@ export const airtableTypeToZodType = (field: AirtableField): ZodTypeAny => {
.with({ type: P.when((t) => DATE_TYPES.has(t)) }, () => z.coerce.date())
.with({ type: P.when((t) => USER_TYPES.has(t)) }, () => userSchema)
.with({ type: P.when((t) => BOOLEAN_TYPES.has(t)) }, () => z.boolean())
.with({ type: "email" }, () => z.string().email())
.with({ type: "url" }, () => z.string().url())
.with({ type: "email" }, () => z.email())
.with({ type: "url" }, () => z.url())
.with(
{ type: "singleSelect", options: { choices: P.array(P.any) } },
({ options }) => {
Expand All @@ -105,7 +105,7 @@ export const airtableTypeToZodType = (field: AirtableField): ZodTypeAny => {
.with({ type: "button" }, () =>
z.object({
label: z.string(),
url: z.string().url().optional(),
url: z.url().optional(),
}),
)
.with(
Expand Down Expand Up @@ -147,7 +147,7 @@ export const airtableTypeToZodType = (field: AirtableField): ZodTypeAny => {
)
.with(
{ type: "multipleLookupValues", options: { result: { type: "object" } } },
() => z.array(z.object({}).passthrough()),
() => z.array(z.looseObject({})),
)
.with({ type: "duration" }, () => z.number())
.otherwise(() => z.unknown());
Expand Down
4 changes: 2 additions & 2 deletions packages/airtable/test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ describe("Airtable to Zod Schema Mapping", () => {
it("should map email to z.string().email()", () => {
const field = airtableFieldFixtures.email;
const schema = airtableTypeToZodType(field);
expect(schema).toBeInstanceOf(z.ZodString);
expect(schema).toBeInstanceOf(z.ZodEmail);
expect(() => schema.parse("not-an-email")).toThrow();
expect(() => schema.parse("test@example.com")).not.toThrow();
});

it("should map url to z.string().url()", () => {
const field = airtableFieldFixtures.url;
const schema = airtableTypeToZodType(field);
expect(schema).toBeInstanceOf(z.ZodString);
expect(schema).toBeInstanceOf(z.ZodURL);
expect(() => schema.parse("not-a-url")).toThrow();
expect(() => schema.parse("https://example.com")).not.toThrow();
});
Expand Down
4 changes: 2 additions & 2 deletions packages/bluesky/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"astro": "^5.10.1",
"astro": "^6.0.8",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3",
"vitest": "^3.2.4"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [
"withastro",
Expand Down
4 changes: 2 additions & 2 deletions packages/csv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"@types/papaparse": "^5.3.15",
"astro": "^5.10.1",
"astro": "^6.0.8",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [
"withastro",
Expand Down
8 changes: 4 additions & 4 deletions packages/feed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"astro": "^5.10.1",
"astro": "^6.0.8",
"msw": "^2.10.2",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [
"withastro",
Expand All @@ -42,7 +42,7 @@
},
"homepage": "https://github.com/ascorbic/astro-loaders",
"dependencies": {
"@rowanmanning/feed-parser": "^2.0.0",
"@ascorbic/loader-utils": "workspace:^"
"@ascorbic/loader-utils": "workspace:^",
"@rowanmanning/feed-parser": "^2.0.0"
}
}
4 changes: 2 additions & 2 deletions packages/feed/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const FeedCategorySchema = z.object({
});

// Legacy schemas from original implementation for exact backward compatibility
export const LegacyNSSchema = z.record(z.string());
export const LegacyNSSchema = z.record(z.string(), z.unknown());

@kydecker kydecker Mar 14, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zod 4 requires two arguments for z.record(): https://zod.dev/v4/changelog?id=drops-single-argument-usage


export const LegacyImageSchema = z.object({
url: z.string().optional(),
Expand Down Expand Up @@ -111,7 +111,7 @@ export const LegacyItemSchema = z
enclosures: z.array(LegacyEnclosureSchema),
meta: LegacyMetaSchema,
})
.and(z.record(z.unknown())); // Allow additional fields
.and(z.record(z.string(), z.unknown())); // Allow additional fields

// Feed schema for the complete feed structure
export const FeedSchema = z.object({
Expand Down
7 changes: 4 additions & 3 deletions packages/mock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"astro": "^5.10.1",
"astro": "^6.0.8",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [
"withastro",
Expand All @@ -43,6 +43,7 @@
"dependencies": {
"@anatine/zod-mock": "^3.13.5",
"@ascorbic/loader-utils": "workspace:^",
"@faker-js/faker": "^9.4.0"
"@faker-js/faker": "^9.4.0",
"zod-to-ts": "^1.2.0"
}
}
48 changes: 29 additions & 19 deletions packages/mock/src/mock-loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Loader } from "astro/loaders";
import { AstroError } from "astro/errors";
import type { ZodSchema } from "astro/zod";
import { z } from "astro/zod";
import { generateMock } from "@anatine/zod-mock";
import { createTypeAlias, printNode, zodToTs } from "zod-to-ts";

interface SharedOptions {
/** The number of entries to generate */
Expand All @@ -17,12 +18,12 @@ interface SharedOptions {
export type MockLoaderOptions = SharedOptions &
(
| {
schema: ZodSchema;
schema: z.ZodType;
loader?: never;
}
| {
loader: Loader;
schema?: ZodSchema;
schema?: z.ZodType;
}
);
/**
Expand All @@ -33,9 +34,9 @@ export function mockLoader({
loader,
...options
}: MockLoaderOptions): Loader {
async function getSchema() {
async function getSchema(): Promise<z.ZodType<Record<string, unknown>>> {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately had to cast getSchema() since there are conflicting types between Astro 6/Zod 4 and @anatine/zod-mock.

if (options.schema) {
return options.schema;
return options.schema as z.ZodType<Record<string, unknown>>;
}

if (!loader) {
Expand All @@ -45,30 +46,30 @@ export function mockLoader({
);
}

if (!loader.schema) {
throw new AstroError(
`The loader "${loader.name}" does not define a schema`,
"Define a schema manually and pass it to the `mockLoader`",
);
if ("createSchema" in loader && loader.createSchema) {
const { schema } = await loader.createSchema();
return schema as z.ZodType<Record<string, unknown>>;
}
if (typeof loader.schema === "function") {
return loader.schema();
} else {
return loader.schema;

if ("schema" in loader && loader.schema) {
return loader.schema as z.ZodType<Record<string, unknown>>;
}
}

const schema = getSchema();
throw new AstroError(
`The loader "${loader.name}" does not define a schema`,
"Define a schema manually and pass it to the `mockLoader`",
);
}

return {
name: "mock-loader",
load: async ({ logger, store }) => {
logger.info(
`Generating mock data${loader?.name ? ` for ${loader.name}` : ""}`,
);
const mockSchema = await schema;
const schema = await getSchema();
for (let i = 0; i < entryCount; i++) {
const data = generateMock(mockSchema, {
const data = generateMock(schema, {
seed: options.seed ? options.seed + i : undefined,
});
const id = options.idField ? data[options.idField] : i;
Expand All @@ -81,7 +82,16 @@ export function mockLoader({
}
logger.info(`Generated ${entryCount} mock entries`);
},
schema: () => schema,
createSchema: async () => {
const schema = await getSchema();
const identifier = "Entry";
const { node } = zodToTs(schema, identifier);
const typeAlias = createTypeAlias(node, identifier);
return {
schema,
types: `export ${printNode(typeAlias)}`,
};
},
};
}

Expand Down
7 changes: 3 additions & 4 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"astro": "^5.10.1",
"astro": "^6.0.8",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [],
"author": "",
Expand All @@ -34,6 +34,5 @@
"url": "git+https://github.com:ascorbic/astro-loaders.git",
"directory": "packages/utils"
},
"homepage": "https://github.com/ascorbic/astro-loaders",
"dependencies": {}
"homepage": "https://github.com/ascorbic/astro-loaders"
}
4 changes: 2 additions & 2 deletions packages/youtube/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"astro": "^5.10.1",
"astro": "^6.0.8",
"msw": "^2.10.2",
"publint": "^0.3.2",
"tsup": "^8.3.6",
"typescript": "^5.7.3"
},
"peerDependencies": {
"astro": "^4.14.0 || ^5.0.0"
"astro": "^4.14.0 || ^5.0.0 || ^6.0.0"
},
"keywords": [
"withastro",
Expand Down
Loading