astro-loader-pocketbase 2.8.2-next.3 → 2.9.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -309,6 +309,17 @@ const blogLive = defineLiveCollection({
309
309
  });
310
310
  ```
311
311
 
312
+ ### Error handling
313
+
314
+ The live content loader follows Astro's standard error handling conventions for live collections. For more information on how to handle errors in your components, see the [Astro documentation on error handling](https://docs.astro.build/en/reference/experimental-flags/live-content-collections/#error-handling).
315
+
316
+ | Error | When it's returned |
317
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
318
+ | `PocketBaseAuthenticationError` | Authentication or authorization fails (missing credentials or invalid token) |
319
+ | `LiveEntryNotFoundError` | The requested entry doesn't exist or doesn't match the applied filters |
320
+ | `LiveCollectionValidationError` | The data returned by PocketBase doesn't match the Zod schema or the `updatedField` doesn't contain valid data |
321
+ | `LiveCollectionError` | Any other error occurs (network errors, invalid filter syntax, PocketBase server errors, etc.) |
322
+
312
323
  ### Caveats
313
324
 
314
325
  Live content loaders do not (yet 🤞🏼) support zod schemas and thus schema generation and entry transformation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-loader-pocketbase",
3
- "version": "2.8.2-next.3",
3
+ "version": "2.9.0-next.1",
4
4
  "description": "A content loader for Astro that uses the PocketBase API",
5
5
  "keywords": [
6
6
  "astro",
@@ -29,8 +29,8 @@
29
29
  "scripts": {
30
30
  "format": "npx prettier . --write --cache",
31
31
  "format:check": "npx prettier . --check --cache",
32
- "lint": "oxlint",
33
- "lint:fix": "oxlint --fix",
32
+ "lint": "oxlint --type-aware",
33
+ "lint:fix": "oxlint --type-aware --fix",
34
34
  "prepare": "husky",
35
35
  "test": "vitest run",
36
36
  "test:e2e": "vitest run $(find test -name '*.e2e-spec.ts')",
@@ -47,11 +47,12 @@
47
47
  "@commitlint/config-conventional": "20.0.0",
48
48
  "@types/node": "24.9.1",
49
49
  "@vitest/coverage-v8": "3.2.4",
50
- "astro": "5.14.8",
50
+ "astro": "5.15.1",
51
51
  "globals": "16.4.0",
52
52
  "husky": "9.1.7",
53
- "lint-staged": "16.2.5",
53
+ "lint-staged": "16.2.6",
54
54
  "oxlint": "1.24.0",
55
+ "oxlint-tsgolint": "^0.3.0",
55
56
  "prettier": "3.6.2",
56
57
  "prettier-plugin-organize-imports": "4.3.0",
57
58
  "prettier-plugin-packagejson": "2.5.19",
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  experimentalPocketbaseLiveLoader,
3
3
  pocketbaseLoader
4
4
  } from "./pocketbase-loader";
5
+ import { PocketBaseAuthenticationError } from "./types/errors";
5
6
  import type {
6
7
  ExperimentalPocketBaseLiveLoaderCollectionFilter,
7
8
  ExperimentalPocketBaseLiveLoaderEntryFilter
@@ -11,7 +12,11 @@ import type {
11
12
  PocketBaseLoaderOptions
12
13
  } from "./types/pocketbase-loader-options.type";
13
14
 
14
- export { experimentalPocketbaseLiveLoader, pocketbaseLoader };
15
+ export {
16
+ experimentalPocketbaseLiveLoader,
17
+ PocketBaseAuthenticationError,
18
+ pocketbaseLoader
19
+ };
15
20
  export type {
16
21
  ExperimentalPocketBaseLiveLoaderCollectionFilter,
17
22
  ExperimentalPocketBaseLiveLoaderEntryFilter,
@@ -1,4 +1,11 @@
1
1
  import type { LoaderContext } from "astro/loaders";
2
+ import { z } from "astro/zod";
3
+ import {
4
+ pocketBaseErrorResponse,
5
+ pocketBaseListResponse
6
+ } from "../types/pocketbase-api-response.type";
7
+ import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
8
+ import { pocketBaseBaseEntry } from "../types/pocketbase-entry.type";
2
9
  import type { PocketBaseLoaderBaseOptions } from "../types/pocketbase-loader-options.type";
3
10
 
4
11
  /**
@@ -67,10 +74,10 @@ export async function cleanupEntries(
67
74
  );
68
75
  }
69
76
  } else {
70
- const reason = await collectionRequest
71
- .json()
72
- .then((data) => data.message);
73
- const errorMessage = `Fetching ids failed with status code ${collectionRequest.status}.\nReason: ${reason}`;
77
+ const errorResponse = pocketBaseErrorResponse.parse(
78
+ await collectionRequest.json()
79
+ );
80
+ const errorMessage = `Fetching ids failed with status code ${collectionRequest.status}.\nReason: ${errorResponse.message}`;
74
81
  context.logger.error(errorMessage);
75
82
  }
76
83
 
@@ -81,7 +88,9 @@ export async function cleanupEntries(
81
88
  }
82
89
 
83
90
  // Get the data from the response
84
- const response = await collectionRequest.json();
91
+ const response = cleanUpEntriesResponse.parse(
92
+ await collectionRequest.json()
93
+ );
85
94
 
86
95
  // Add the ids to the set
87
96
  for (const item of response.items) {
@@ -97,7 +106,10 @@ export async function cleanupEntries(
97
106
 
98
107
  // Create a mapping from PocketBase IDs to store keys for proper cleanup
99
108
  const storedIds = new Map<string, string>(
100
- context.store.values().map((entry) => [entry.data.id as string, entry.id])
109
+ context.store
110
+ .values()
111
+ // oxlint-disable-next-line no-unsafe-type-assertion
112
+ .map((entry) => [(entry.data as PocketBaseEntry).id, entry.id])
101
113
  );
102
114
 
103
115
  // Check which PocketBase IDs are missing from the server response
@@ -114,3 +126,12 @@ export async function cleanupEntries(
114
126
  context.logger.info(`Cleaned up ${cleanedUp} old entries.`);
115
127
  }
116
128
  }
129
+
130
+ /**
131
+ * The response schema for cleaning up entries.
132
+ */
133
+ const cleanUpEntriesResponse = pocketBaseListResponse
134
+ .omit({ items: true })
135
+ .extend({
136
+ items: z.array(pocketBaseBaseEntry.pick({ id: true }))
137
+ });
@@ -1,3 +1,12 @@
1
+ import {
2
+ LiveCollectionError,
3
+ LiveEntryNotFoundError
4
+ } from "astro/content/runtime";
5
+ import { PocketBaseAuthenticationError } from "../types/errors";
6
+ import {
7
+ pocketBaseErrorResponse,
8
+ pocketBaseListResponse
9
+ } from "../types/pocketbase-api-response.type";
1
10
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
2
11
  import type { ExperimentalPocketBaseLiveLoaderCollectionFilter } from "../types/pocketbase-live-loader-filter.type";
3
12
  import type { PocketBaseLoaderBaseOptions } from "../types/pocketbase-loader-options.type";
@@ -68,27 +77,42 @@ export async function fetchCollection<TEntry extends PocketBaseEntry>(
68
77
  options.superuserCredentials &&
69
78
  "impersonateToken" in options.superuserCredentials
70
79
  ) {
71
- throw new Error("The given impersonate token is not valid.");
80
+ throw new PocketBaseAuthenticationError(
81
+ options.collectionName,
82
+ "The given impersonate token is not valid."
83
+ );
72
84
  } else {
73
- throw new Error(
85
+ throw new PocketBaseAuthenticationError(
86
+ options.collectionName,
74
87
  "The collection is not accessible without superuser rights. Please provide superuser credentials in the config."
75
88
  );
76
89
  }
77
90
  }
78
91
 
92
+ if (collectionRequest.status === 404) {
93
+ throw new LiveEntryNotFoundError(options.collectionName, {
94
+ ...collectionFilter
95
+ });
96
+ }
97
+
79
98
  // Get the reason for the error
80
- const reason = await collectionRequest
81
- .json()
82
- .then((data) => data.message);
83
- const errorMessage = `Fetching data failed with status code ${collectionRequest.status}.\nReason: ${reason}`;
84
- throw new Error(errorMessage);
99
+ const errorResponse = pocketBaseErrorResponse.parse(
100
+ await collectionRequest.json()
101
+ );
102
+ throw new LiveCollectionError(
103
+ options.collectionName,
104
+ errorResponse.message
105
+ );
85
106
  }
86
107
 
87
108
  // Get the data from the response
88
- const response = await collectionRequest.json();
109
+ const response = pocketBaseListResponse.parse(
110
+ await collectionRequest.json()
111
+ );
89
112
 
90
113
  // Return current chunk
91
- await chunkLoaded(response.items);
114
+ // oxlint-disable-next-line no-unsafe-type-assertion
115
+ await chunkLoaded(response.items as Array<TEntry>);
92
116
 
93
117
  // Update the page and total pages
94
118
  page = response.page;
@@ -1,4 +1,11 @@
1
+ import {
2
+ LiveCollectionError,
3
+ LiveEntryNotFoundError
4
+ } from "astro/content/runtime";
5
+ import { PocketBaseAuthenticationError } from "../types/errors";
6
+ import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
1
7
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
8
+ import { pocketBaseEntry } from "../types/pocketbase-entry.type";
2
9
  import type { ExperimentalPocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
3
10
  import { combineFieldsForRequest } from "../utils/combine-fields-for-request";
4
11
  import { formatFields } from "../utils/format-fields";
@@ -43,22 +50,34 @@ export async function fetchEntry<TEntry extends PocketBaseEntry>(
43
50
  options.superuserCredentials &&
44
51
  "impersonateToken" in options.superuserCredentials
45
52
  ) {
46
- throw new Error("The given impersonate token is not valid.");
53
+ throw new PocketBaseAuthenticationError(
54
+ options.collectionName,
55
+ "The given impersonate token is not valid."
56
+ );
47
57
  } else {
48
- throw new Error(
58
+ throw new PocketBaseAuthenticationError(
59
+ options.collectionName,
49
60
  "The entry is not accessible without superuser rights. Please provide superuser credentials in the config."
50
61
  );
51
62
  }
52
63
  }
53
64
 
65
+ if (entryRequest.status === 404) {
66
+ throw new LiveEntryNotFoundError(options.collectionName, id);
67
+ }
68
+
54
69
  // Get the reason for the error
55
- const reason = await entryRequest.json().then((data) => data.message);
56
- const errorMessage = `Fetching entry "${id}" from collection "${options.collectionName}" failed.\nReason: ${reason}`;
57
- throw new Error(errorMessage);
70
+ const errorResponse = pocketBaseErrorResponse.parse(
71
+ await entryRequest.json()
72
+ );
73
+ throw new LiveCollectionError(
74
+ options.collectionName,
75
+ errorResponse.message
76
+ );
58
77
  }
59
78
 
60
79
  // Get the data from the response
61
- const response = await entryRequest.json();
62
-
63
- return response;
80
+ const response = pocketBaseEntry.parse(await entryRequest.json());
81
+ // oxlint-disable-next-line no-unsafe-type-assertion
82
+ return response as TEntry;
64
83
  }
@@ -1,4 +1,5 @@
1
1
  import type { LiveDataCollection, LiveDataEntry } from "astro";
2
+ import { LiveCollectionError } from "astro/content/runtime";
2
3
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
3
4
  import type { ExperimentalPocketBaseLiveLoaderCollectionFilter } from "../types/pocketbase-live-loader-filter.type";
4
5
  import type { ExperimentalPocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
@@ -14,13 +15,12 @@ export async function liveCollectionLoader<TEntry extends PocketBaseEntry>(
14
15
  | undefined,
15
16
  options: ExperimentalPocketBaseLiveLoaderOptions,
16
17
  token: string | undefined
17
- ): Promise<LiveDataCollection<TEntry> | { error: Error }> {
18
+ ): Promise<LiveDataCollection<TEntry> | { error: LiveCollectionError }> {
18
19
  const entries: Array<LiveDataEntry<TEntry>> = [];
19
20
 
20
21
  try {
21
22
  await fetchCollection<TEntry>(
22
23
  options,
23
- // oxlint-disable-next-line require-await
24
24
  async (chunk) => {
25
25
  entries.push(...chunk.map((entry) => parseLiveEntry(entry, options)));
26
26
  },
@@ -28,7 +28,23 @@ export async function liveCollectionLoader<TEntry extends PocketBaseEntry>(
28
28
  collectionFilter
29
29
  );
30
30
  } catch (error) {
31
- return { error: error as Error };
31
+ if (error instanceof LiveCollectionError) {
32
+ return { error };
33
+ }
34
+
35
+ if (error instanceof Error) {
36
+ return {
37
+ error: new LiveCollectionError(
38
+ options.collectionName,
39
+ error.message,
40
+ error
41
+ )
42
+ };
43
+ }
44
+
45
+ return {
46
+ error: new LiveCollectionError(options.collectionName, String(error))
47
+ };
32
48
  }
33
49
 
34
50
  return {
@@ -1,4 +1,5 @@
1
1
  import type { LiveDataEntry } from "astro";
2
+ import { LiveCollectionError } from "astro/content/runtime";
2
3
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
3
4
  import type { ExperimentalPocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
4
5
  import { fetchEntry } from "./fetch-entry";
@@ -11,11 +12,27 @@ export async function liveEntryLoader<TEntry extends PocketBaseEntry>(
11
12
  id: string,
12
13
  options: ExperimentalPocketBaseLiveLoaderOptions,
13
14
  token: string | undefined
14
- ): Promise<LiveDataEntry<TEntry> | { error: Error }> {
15
+ ): Promise<LiveDataEntry<TEntry> | { error: LiveCollectionError }> {
15
16
  try {
16
17
  const entry = await fetchEntry<TEntry>(id, options, token);
17
18
  return parseLiveEntry(entry, options);
18
19
  } catch (error) {
19
- return { error: error as Error };
20
+ if (error instanceof LiveCollectionError) {
21
+ return { error };
22
+ }
23
+
24
+ if (error instanceof Error) {
25
+ return {
26
+ error: new LiveCollectionError(
27
+ options.collectionName,
28
+ error.message,
29
+ error
30
+ )
31
+ };
32
+ }
33
+
34
+ return {
35
+ error: new LiveCollectionError(options.collectionName, String(error))
36
+ };
20
37
  }
21
38
  }
@@ -1,4 +1,6 @@
1
1
  import type { LiveDataEntry } from "astro";
2
+ import { LiveCollectionValidationError } from "astro/content/runtime";
3
+ import { z } from "astro/zod";
2
4
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
3
5
  import type { ExperimentalPocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
4
6
 
@@ -17,18 +19,15 @@ export function parseLiveEntry<TEntry extends PocketBaseEntry>(
17
19
  // use it as the last modified date cache hint
18
20
  if (options.updatedField && entry[options.updatedField]) {
19
21
  const value = `${entry[options.updatedField]}`;
20
- try {
21
- const date = new Date(value);
22
- if (isNaN(date.getTime())) {
23
- throw new TypeError("Invalid date");
24
- }
25
-
26
- lastModified = date;
27
- } catch {
28
- console.warn(
29
- `Entry ${entry.id} of collection ${options.collectionName} has an invalid date in ${options.updatedField}: ${value}`
22
+ const date = z.coerce.date().safeParse(value);
23
+ if (!date.success) {
24
+ throw new LiveCollectionValidationError(
25
+ options.collectionName,
26
+ entry.id,
27
+ date.error
30
28
  );
31
29
  }
30
+ lastModified = date.data;
32
31
  }
33
32
 
34
33
  if (!options.contentFields) {
@@ -1,4 +1,5 @@
1
1
  import type { LiveDataCollection, LiveDataEntry } from "astro";
2
+ import type { LiveCollectionError } from "astro/content/runtime";
2
3
  import type { LiveLoader, Loader } from "astro/loaders";
3
4
  import type { ZodSchema } from "astro/zod";
4
5
  import { liveCollectionLoader } from "./loader/live-collection-loader";
@@ -45,7 +46,7 @@ export function pocketbaseLoader(options: PocketBaseLoaderOptions): Loader {
45
46
  const token = await tokenPromise;
46
47
 
47
48
  // Generate the schema for the collection according to the API
48
- return await generateSchema(options, token);
49
+ return generateSchema(options, token);
49
50
  }
50
51
  };
51
52
  }
@@ -65,7 +66,8 @@ export function experimentalPocketbaseLiveLoader<
65
66
  ): LiveLoader<
66
67
  TEntry,
67
68
  ExperimentalPocketBaseLiveLoaderEntryFilter,
68
- ExperimentalPocketBaseLiveLoaderCollectionFilter
69
+ ExperimentalPocketBaseLiveLoaderCollectionFilter,
70
+ LiveCollectionError
69
71
  > {
70
72
  // Create shared promise for the superuser token, which can be reused
71
73
  const tokenPromise = createTokenPromise(options);
@@ -74,19 +76,21 @@ export function experimentalPocketbaseLiveLoader<
74
76
  name: "pocketbase-live-loader",
75
77
  loadCollection: async ({
76
78
  filter
77
- }): Promise<LiveDataCollection<TEntry> | { error: Error }> => {
79
+ }): Promise<
80
+ LiveDataCollection<TEntry> | { error: LiveCollectionError }
81
+ > => {
78
82
  const token = await tokenPromise;
79
83
 
80
84
  // Load entries from the collection
81
- return await liveCollectionLoader(filter, options, token);
85
+ return liveCollectionLoader<TEntry>(filter, options, token);
82
86
  },
83
87
  loadEntry: async ({
84
88
  filter
85
- }): Promise<LiveDataEntry<TEntry> | { error: Error }> => {
89
+ }): Promise<LiveDataEntry<TEntry> | { error: LiveCollectionError }> => {
86
90
  const token = await tokenPromise;
87
91
 
88
92
  // Load a single entry from the collection
89
- return await liveEntryLoader(filter.id, options, token);
93
+ return liveEntryLoader<TEntry>(filter.id, options, token);
90
94
  }
91
95
  };
92
96
  }
@@ -1,5 +1,7 @@
1
+ import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
1
2
  import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
2
3
  import type { PocketBaseCollection } from "../types/pocketbase-schema.type";
4
+ import { pocketBaseCollection } from "../types/pocketbase-schema.type";
3
5
 
4
6
  /**
5
7
  * Fetches the schema for the specified collection from the PocketBase instance.
@@ -26,13 +28,16 @@ export async function getRemoteSchema(
26
28
 
27
29
  // If the request was not successful, try another method
28
30
  if (!schemaRequest.ok) {
29
- const reason = await schemaRequest.json().then((data) => data.message);
30
- const errorMessage = `Fetching schema from ${options.collectionName} failed with status code ${schemaRequest.status}.\nReason: ${reason}`;
31
+ const errorResponse = pocketBaseErrorResponse.parse(
32
+ await schemaRequest.json()
33
+ );
34
+ const errorMessage = `Fetching schema from ${options.collectionName} failed with status code ${schemaRequest.status}.\nReason: ${errorResponse.message}`;
31
35
  console.error(errorMessage);
32
36
 
33
37
  return undefined;
34
38
  }
35
39
 
36
40
  // Get the schema from the response
37
- return await schemaRequest.json();
41
+ const response = pocketBaseCollection.parse(await schemaRequest.json());
42
+ return response;
38
43
  }
@@ -46,6 +46,7 @@ export function parseSchema(
46
46
  let fieldType;
47
47
 
48
48
  // Determine the field type and create the corresponding Zod type
49
+ // oxlint-disable-next-line switch-exhaustiveness-check
49
50
  switch (field.type) {
50
51
  case "number":
51
52
  fieldType = z.number();
@@ -77,6 +78,7 @@ export function parseSchema(
77
78
  }
78
79
 
79
80
  // Create an enum for the select values
81
+ // oxlint-disable-next-line no-unsafe-type-assertion
80
82
  const values = z.enum(field.values as [string, ...Array<string>]);
81
83
 
82
84
  // Parse the field type based on the number of values it can have
@@ -1,6 +1,7 @@
1
1
  import fs from "fs/promises";
2
2
  import path from "path";
3
3
  import type { PocketBaseCollection } from "../types/pocketbase-schema.type";
4
+ import { pocketBaseDatabase } from "../types/pocketbase-schema.type";
4
5
 
5
6
  /**
6
7
  * Reads the local PocketBase schema file and returns the schema for the specified collection.
@@ -17,15 +18,15 @@ export async function readLocalSchema(
17
18
  try {
18
19
  // Read the schema file
19
20
  const schemaFile = await fs.readFile(realPath, "utf-8");
20
- const database: Array<PocketBaseCollection> = JSON.parse(schemaFile);
21
+ const fileContent = pocketBaseDatabase.safeParse(JSON.parse(schemaFile));
21
22
 
22
23
  // Check if the database file is valid
23
- if (!database || !Array.isArray(database)) {
24
+ if (!fileContent.success) {
24
25
  throw new Error("Invalid schema file");
25
26
  }
26
27
 
27
28
  // Find and return the schema for the collection
28
- const schema = database.find(
29
+ const schema = fileContent.data.find(
29
30
  (collection) => collection.name === collectionName
30
31
  );
31
32
 
@@ -1,3 +1,4 @@
1
+ import { z } from "astro/zod";
1
2
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
2
3
  import type { PocketBaseSchemaEntry } from "../types/pocketbase-schema.type";
3
4
 
@@ -18,7 +19,7 @@ export function transformFiles(
18
19
  const fieldName = field.name;
19
20
 
20
21
  if (field.maxSelect === 1) {
21
- const fileName = entry[fieldName] as string | undefined;
22
+ const fileName = z.optional(z.string()).parse(entry[fieldName]);
22
23
  // Check if a file name is present
23
24
  if (!fileName) {
24
25
  continue;
@@ -32,7 +33,7 @@ export function transformFiles(
32
33
  fileName
33
34
  );
34
35
  } else {
35
- const fileNames = entry[fieldName] as Array<string> | undefined;
36
+ const fileNames = z.optional(z.array(z.string())).parse(entry[fieldName]);
36
37
  // Check if file names are present
37
38
  if (!fileNames) {
38
39
  continue;
@@ -0,0 +1,19 @@
1
+ import { LiveCollectionError } from "astro/content/runtime";
2
+
3
+ /**
4
+ * Error thrown when there is an authentication issue with PocketBase.
5
+ */
6
+ export class PocketBaseAuthenticationError extends LiveCollectionError {
7
+ constructor(collection: string, message: string) {
8
+ super(collection, message);
9
+ this.name = "PocketBaseAuthenticationError";
10
+ }
11
+
12
+ static is(error: unknown): error is PocketBaseAuthenticationError {
13
+ // This is similar to the original implementation in Astro itself.
14
+ return (
15
+ // oxlint-disable-next-line no-unsafe-type-assertion
16
+ !!error && (error as Error)?.name === "PocketBaseAuthenticationError"
17
+ );
18
+ }
19
+ }
@@ -0,0 +1,40 @@
1
+ import { z } from "astro/zod";
2
+ import { pocketBaseEntry } from "./pocketbase-entry.type";
3
+
4
+ /**
5
+ * The schema for a PocketBase error response.
6
+ */
7
+ export const pocketBaseErrorResponse = z.object({
8
+ /**
9
+ * The error message returned by PocketBase.
10
+ */
11
+ message: z.string()
12
+ });
13
+
14
+ /**
15
+ * The schema for a PocketBase list response.
16
+ */
17
+ export const pocketBaseListResponse = z.object({
18
+ /**
19
+ * Current page number.
20
+ */
21
+ page: z.number(),
22
+ /**
23
+ * Total number of pages available.
24
+ */
25
+ totalPages: z.number(),
26
+ /**
27
+ * Array of items in the current page.
28
+ */
29
+ items: z.array(pocketBaseEntry)
30
+ });
31
+
32
+ /**
33
+ * The schema for a PocketBase login response.
34
+ */
35
+ export const pocketBaseLoginResponse = z.object({
36
+ /**
37
+ * The authentication token returned by PocketBase.
38
+ */
39
+ token: z.string()
40
+ });
@@ -1,22 +1,34 @@
1
+ import { z } from "astro/zod";
2
+
1
3
  /**
2
- * Base interface for all PocketBase entries.
4
+ * Base schema for all PocketBase entries.
3
5
  */
4
- interface PocketBaseBaseEntry {
6
+ export const pocketBaseBaseEntry = z.object({
5
7
  /**
6
8
  * ID of the entry.
7
9
  */
8
- id: string;
10
+ id: z.string(),
9
11
  /**
10
12
  * ID of the collection the entry belongs to.
11
13
  */
12
- collectionId: string;
14
+ collectionId: z.string(),
13
15
  /**
14
16
  * Name of the collection the entry belongs to.
15
17
  */
16
- collectionName: string;
17
- }
18
+ collectionName: z.string()
19
+ });
20
+
21
+ /**
22
+ * Base interface for all PocketBase entries.
23
+ */
24
+ export type PocketBaseBaseEntry = z.infer<typeof pocketBaseBaseEntry>;
25
+
26
+ /**
27
+ * Schema for a PocketBase entry.
28
+ */
29
+ export const pocketBaseEntry = pocketBaseBaseEntry.passthrough();
18
30
 
19
31
  /**
20
32
  * Type for a PocketBase entry.
21
33
  */
22
- export type PocketBaseEntry = PocketBaseBaseEntry & Record<string, unknown>;
34
+ export type PocketBaseEntry = z.infer<typeof pocketBaseEntry>;
@@ -1,60 +1,92 @@
1
+ import { z } from "astro/zod";
2
+
1
3
  /**
2
4
  * Entry for a collections schema in PocketBase.
3
5
  */
4
- export interface PocketBaseSchemaEntry {
6
+ export const pocketBaseSchemaEntry = z.object({
5
7
  /**
6
8
  * Flag to indicate if the field is hidden.
7
9
  * Hidden fields are not returned in the API response.
8
10
  */
9
- hidden: boolean;
11
+ hidden: z.optional(z.boolean()),
10
12
  /**
11
13
  * Name of the field.
12
14
  */
13
- name: string;
15
+ name: z.string(),
14
16
  /**
15
17
  * Type of the field.
16
18
  */
17
- type: string;
19
+ type: z.enum([
20
+ "text",
21
+ "editor",
22
+ "number",
23
+ "bool",
24
+ "email",
25
+ "url",
26
+ "date",
27
+ "autodate",
28
+ "select",
29
+ "file",
30
+ "relation",
31
+ "json",
32
+ "geoPoint",
33
+ "password"
34
+ ]),
18
35
  /**
19
36
  * Whether the field is required.
20
37
  */
21
- required: boolean;
38
+ required: z.optional(z.boolean()),
22
39
  /**
23
40
  * Values for a select field.
24
41
  * This is only present if the field type is "select".
25
42
  */
26
- values?: Array<string>;
43
+ values: z.optional(z.array(z.string())),
27
44
  /**
28
45
  * Maximum number of values for a select field.
29
46
  * This is only present on "select", "relation", and "file" fields.
30
47
  */
31
- maxSelect?: number;
48
+ maxSelect: z.optional(z.number()),
32
49
  /**
33
50
  * Whether the field is filled when the entry is created.
34
51
  * This is only present on "autodate" fields.
35
52
  */
36
- onCreate?: boolean;
53
+ onCreate: z.optional(z.boolean()),
37
54
  /**
38
55
  * Whether the field is updated when the entry is updated.
39
56
  * This is only present on "autodate" fields.
40
57
  */
41
- onUpdate?: boolean;
42
- }
58
+ onUpdate: z.optional(z.boolean())
59
+ });
60
+
61
+ /**
62
+ * Entry for a collections schema in PocketBase.
63
+ */
64
+ export type PocketBaseSchemaEntry = z.infer<typeof pocketBaseSchemaEntry>;
43
65
 
44
66
  /**
45
67
  * Schema for a PocketBase collection.
46
68
  */
47
- export interface PocketBaseCollection {
69
+ export const pocketBaseCollection = z.object({
48
70
  /**
49
71
  * Name of the collection.
50
72
  */
51
- name: string;
73
+ name: z.string(),
52
74
  /**
53
75
  * Type of the collection.
54
76
  */
55
- type: "base" | "view" | "auth";
77
+ type: z.enum(["base", "view", "auth"]),
56
78
  /**
57
79
  * Schema of the collection.
58
80
  */
59
- fields: Array<PocketBaseSchemaEntry>;
60
- }
81
+ fields: z.array(pocketBaseSchemaEntry)
82
+ });
83
+
84
+ /**
85
+ * Type for a PocketBase collection.
86
+ */
87
+ export type PocketBaseCollection = z.infer<typeof pocketBaseCollection>;
88
+
89
+ /**
90
+ * Schema for an entire PocketBase database.
91
+ */
92
+ export const pocketBaseDatabase = z.array(pocketBaseCollection);
@@ -1,4 +1,8 @@
1
1
  import type { AstroIntegrationLogger } from "astro";
2
+ import {
3
+ pocketBaseErrorResponse,
4
+ pocketBaseLoginResponse
5
+ } from "../types/pocketbase-api-response.type";
2
6
 
3
7
  /**
4
8
  * This function will get a superuser token from the given PocketBase instance.
@@ -52,8 +56,10 @@ export async function getSuperuserToken(
52
56
  return getSuperuserToken(url, superuserCredentials, logger);
53
57
  }
54
58
 
55
- const reason = await loginRequest.json().then((data) => data.message);
56
- const errorMessage = `The given email / password for ${url} was not correct. Astro can't generate type definitions automatically and may not have access to all resources.\nReason: ${reason}`;
59
+ const errorResponse = pocketBaseErrorResponse.parse(
60
+ await loginRequest.json()
61
+ );
62
+ const errorMessage = `The given email / password for ${url} was not correct. Astro can't generate type definitions automatically and may not have access to all resources.\nReason: ${errorResponse.message}`;
57
63
  if (logger) {
58
64
  logger.error(errorMessage);
59
65
  } else {
@@ -63,5 +69,6 @@ export async function getSuperuserToken(
63
69
  }
64
70
 
65
71
  // Return the token
66
- return await loginRequest.json().then((data) => data.token);
72
+ const response = pocketBaseLoginResponse.parse(await loginRequest.json());
73
+ return response.token;
67
74
  }