Skip to content
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
16 changes: 12 additions & 4 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,18 @@
"typescript/no-unsafe-function-type": "error",
"typescript/no-unsafe-member-access": "error",
"typescript/no-unsafe-return": "error",
"typescript/no-useless-default-assignment": "warn",
"typescript/only-throw-error": "error",
"typescript/prefer-ts-expect-error": "error",
"typescript/restrict-plus-operands": "error",
"typescript/return-await": "error",
"typescript/switch-exhaustiveness-check": "error",
"typescript/strict-void-return": "warn",
"typescript/switch-exhaustiveness-check": [
"error",
{
"considerDefaultExhaustiveForUnions": true
}
],
"unicorn/consistent-empty-array-spread": "warn",
"unicorn/explicit-length-check": "warn",
"unicorn/new-for-builtins": "warn",
Expand Down Expand Up @@ -240,9 +247,7 @@
// Too many false positives, disabled for now until oxlint improves the rule
// "eslint/no-useless-assignment": "warn",
"typescript/no-unnecessary-condition": "warn",
"typescript/no-useless-default-assignment": "warn",
"typescript/prefer-optional-chain": "warn",
"typescript/strict-void-return": "warn"
"typescript/prefer-optional-chain": "warn"
},
"overrides": [
{
Expand All @@ -253,6 +258,9 @@
"eslint/max-lines-per-function": "off",
"eslint/max-nested-callbacks": "off",
"jest/prefer-to-contain": "warn",
"jest/require-to-throw-message": "off",
"jest/valid-describe-callback": "off",
"jest/no-conditional-expect": "off",
"typescript/no-unsafe-argument": "off",
"typescript/no-unsafe-assignment": "off",
"typescript/no-unsafe-call": "off",
Expand Down
577 changes: 296 additions & 281 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "astro-loader-pocketbase",
"version": "3.0.2",
"version": "3.1.0-next.2",
"description": "A content loader for Astro that uses the PocketBase API",
"keywords": [
"astro",
Expand Down Expand Up @@ -50,20 +50,20 @@
"@commitlint/cli": "20.5.0",
"@commitlint/config-conventional": "20.5.0",
"@types/node": "24.10.1",
"@vitest/coverage-v8": "4.1.4",
"astro": "6.1.5",
"@vitest/coverage-v8": "4.1.5",
"astro": "6.1.9",
"globals": "17.5.0",
"husky": "9.1.7",
"lint-staged": "16.4.0",
"oxfmt": "0.44.0",
"oxlint": "1.59.0",
"oxlint-tsgolint": "0.20.0",
"vitest": "4.1.4",
"oxfmt": "0.46.0",
"oxlint": "1.61.0",
"oxlint-tsgolint": "0.21.1",
"vitest": "4.1.5",
"zod-to-ts": "2.0.0"
},
"peerDependencies": {
"astro": "^6.0.0",
"zod-to-ts": "^2.0.0"
},
"packageManager": "npm@11.12.1"
"packageManager": "npm@11.13.0"
}
16 changes: 13 additions & 3 deletions src/schema/generate-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ import { transformFiles } from "./transform-files";
* Basic schema for every PocketBase collection.
*/
const BASIC_SCHEMA = z.object({
id: z.string(),
collectionId: z.string(),
collectionName: z.string()
id: z.string().meta({
title: "id",
description: "The unique identifier for the entry."
}),
collectionId: z.string().meta({
title: "collectionId",
description:
"The unique identifier for the collection the entity belongs to."
}),
collectionName: z.string().meta({
title: "collectionName",
description: "The name of the collection the entity belongs to."
})
});

/**
Expand Down
27 changes: 25 additions & 2 deletions src/schema/parse-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export function parseSchema(
let fieldType: z.ZodType;

// Determine the field type and create the corresponding Zod type
// oxlint-disable-next-line switch-exhaustiveness-check
switch (field.type) {
case "number":
fieldType = z.number();
Expand Down Expand Up @@ -120,7 +119,11 @@ export function parseSchema(
}

// Add the field to the fields object
fields[field.name] = fieldType;
fields[field.name] = fieldType.meta({
id: field.id,
title: field.name,
description: getFieldDescription(field)
});
}

return fields;
Expand All @@ -145,3 +148,23 @@ function parseSingleOrMultipleValues(

return z.array(type);
}

/**
* Get the description for a field based on its help text and type.
*/
function getFieldDescription(field: PocketBaseSchemaEntry): string | undefined {
switch (true) {
case !!field.help:
return field.help;
case field.type === "autodate" && field.onUpdate:
return "Date when the entry was last updated. This field is automatically updated by PocketBase whenever the entry is updated.";
case field.type === "autodate" && field.onCreate:
return "Date when the entry was created. This field is automatically set by PocketBase when the entry is created.";
case field.name === "id":
return "The unique identifier for the entry.";
case field.hidden:
return "This field is hidden and may require superuser credentials to access.";
default:
return undefined;
}
}
9 changes: 9 additions & 0 deletions src/types/pocketbase-schema.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ export const pocketBaseSchemaEntry = z.object({
* Hidden fields are not returned in the API response.
*/
hidden: z.optional(z.boolean()),
/**
* Unique identifier for the field.
*/
id: z.string(),
/**
* Name of the field.
*/
name: z.string(),
/**
* Help text for the field.
* This is only present if the field has help text defined.
*/
help: z.optional(z.string()),
/**
* Type of the field.
*/
Expand Down
4 changes: 4 additions & 0 deletions test/__snapshots__/pocketbase-loader.e2e-spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

exports[`pocketbaseLoader > createSchema function > should return valid schema for all field types 1`] = `
"export type Entry = {
/** The unique identifier for the entry. */
id: string;
/** The unique identifier for the collection the entity belongs to. */
collectionId: string;
/** The name of the collection the entity belongs to. */
collectionName: string;
bool_field: boolean;
number_field: number;
Expand All @@ -12,6 +15,7 @@ exports[`pocketbaseLoader > createSchema function > should return valid schema f
url_field?: string | undefined;
editor_field?: string | undefined;
date_field?: Date | undefined;
/** Date when the entry was created. This field is automatically set by PocketBase when the entry is created. */
autodate_field: Date;
select_field?: ("option1" | "option2") | undefined;
file_field?: string[] | undefined;
Expand Down
2 changes: 1 addition & 1 deletion test/_mocks/insert-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { PocketBaseLoaderOptions } from "../../src/types/pocketbase-loader-
import type { PocketBaseSchemaEntry } from "../../src/types/pocketbase-schema.type";

export async function insertCollection(
fields: Array<PocketBaseSchemaEntry>,
fields: Array<Omit<PocketBaseSchemaEntry, "id">>,
options: PocketBaseLoaderOptions,
superuserToken: string
): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion test/_mocks/test-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const fields = [
// { name: "relation_field", type: "relation" },
{ name: "json_field", type: "json" },
{ name: "geopoint_field", type: "geoPoint" }
] as const satisfies Array<PocketBaseSchemaEntry>;
] as const satisfies Array<Omit<PocketBaseSchemaEntry, "id">>;

type Entry = Record<(typeof fields)[number]["name"], unknown>;

Expand Down
2 changes: 2 additions & 0 deletions test/schema/__snapshots__/generate-type.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`generateType > should generate typescript type for complex entry 1`] = `
"export type Entry = {
age: number;
/** Indicates if the person is an adult */
isAdult: boolean;
birthday: Date;
location: {
Expand All @@ -19,6 +20,7 @@ exports[`generateType > should generate typescript type for complex entry 1`] =
exports[`generateType > should generate typescript type for transformed entry 1`] = `
"export type Entry = {
age: number;
/** Indicates if the person is an adult */
isAdult: boolean;
location: {
lon: number;
Expand Down
8 changes: 8 additions & 0 deletions test/schema/__snapshots__/get-remote-schema.e2e-spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,56 @@ exports[`getRemoteSchema > should return schema if fetch request is successful 1
{
"fields": [
{
"help": "",
"hidden": false,
"name": "id",
"required": true,
"type": "text",
},
{
"help": "",
"hidden": true,
"name": "password",
"required": true,
"type": "password",
},
{
"help": "",
"hidden": true,
"name": "tokenKey",
"required": true,
"type": "text",
},
{
"help": "",
"hidden": false,
"name": "email",
"required": true,
"type": "email",
},
{
"help": "",
"hidden": false,
"name": "emailVisibility",
"required": false,
"type": "bool",
},
{
"help": "",
"hidden": false,
"name": "verified",
"required": false,
"type": "bool",
},
{
"help": "",
"hidden": false,
"name": "name",
"required": false,
"type": "text",
},
{
"help": "",
"hidden": false,
"maxSelect": 1,
"name": "avatar",
Expand Down
12 changes: 10 additions & 2 deletions test/schema/generate-type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ describe("generateType", () => {
test("should generate typescript type for complex entry", () => {
const schema = z.object({
age: z.number(),
isAdult: z.boolean(),
isAdult: z.boolean().meta({
id: "isAdult",
name: "isAdult",
description: "Indicates if the person is an adult"
}),
birthday: z.coerce.date(),
location: z.object({
lon: z.number(),
Expand All @@ -25,7 +29,11 @@ describe("generateType", () => {
test("should generate typescript type for transformed entry", () => {
const schema = z.object({
age: z.number(),
isAdult: z.boolean(),
isAdult: z.boolean().meta({
id: "isAdult",
name: "isAdult",
description: "Indicates if the person is an adult"
}),
location: z.object({
lon: z.number(),
lat: z.number()
Expand Down
6 changes: 6 additions & 0 deletions test/schema/get-remote-schema.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ describe("getRemoteSchema", () => {

assert(result, "Schema is not defined.");

for (const field of result.fields) {
// @ts-expect-error - Id is technically required, but we want to ignore it for snapshot testing
// since it is generated and can change between test runs thus causing snapshot failures
delete field.id;
}

expect(result).toMatchSnapshot();
});
});
Loading