astro-loader-pocketbase 2.8.2-next.4 → 2.9.0-next.2

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.4",
3
+ "version": "2.9.0-next.2",
4
4
  "description": "A content loader for Astro that uses the PocketBase API",
5
5
  "keywords": [
6
6
  "astro",
@@ -46,7 +46,7 @@
46
46
  "@commitlint/cli": "20.1.0",
47
47
  "@commitlint/config-conventional": "20.0.0",
48
48
  "@types/node": "24.9.1",
49
- "@vitest/coverage-v8": "3.2.4",
49
+ "@vitest/coverage-v8": "4.0.3",
50
50
  "astro": "5.15.1",
51
51
  "globals": "16.4.0",
52
52
  "husky": "9.1.7",
@@ -57,7 +57,7 @@
57
57
  "prettier-plugin-organize-imports": "4.3.0",
58
58
  "prettier-plugin-packagejson": "2.5.19",
59
59
  "typescript": "5.9.3",
60
- "vitest": "3.2.4"
60
+ "vitest": "4.0.3"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "astro": "^5.10.0"
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,3 +1,8 @@
1
+ import {
2
+ LiveCollectionError,
3
+ LiveEntryNotFoundError
4
+ } from "astro/content/runtime";
5
+ import { PocketBaseAuthenticationError } from "../types/errors";
1
6
  import {
2
7
  pocketBaseErrorResponse,
3
8
  pocketBaseListResponse
@@ -72,20 +77,32 @@ export async function fetchCollection<TEntry extends PocketBaseEntry>(
72
77
  options.superuserCredentials &&
73
78
  "impersonateToken" in options.superuserCredentials
74
79
  ) {
75
- 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
+ );
76
84
  } else {
77
- throw new Error(
85
+ throw new PocketBaseAuthenticationError(
86
+ options.collectionName,
78
87
  "The collection is not accessible without superuser rights. Please provide superuser credentials in the config."
79
88
  );
80
89
  }
81
90
  }
82
91
 
92
+ if (collectionRequest.status === 404) {
93
+ throw new LiveEntryNotFoundError(options.collectionName, {
94
+ ...collectionFilter
95
+ });
96
+ }
97
+
83
98
  // Get the reason for the error
84
99
  const errorResponse = pocketBaseErrorResponse.parse(
85
100
  await collectionRequest.json()
86
101
  );
87
- const errorMessage = `Fetching data failed with status code ${collectionRequest.status}.\nReason: ${errorResponse.message}`;
88
- throw new Error(errorMessage);
102
+ throw new LiveCollectionError(
103
+ options.collectionName,
104
+ errorResponse.message
105
+ );
89
106
  }
90
107
 
91
108
  // Get the data from the response
@@ -1,3 +1,8 @@
1
+ import {
2
+ LiveCollectionError,
3
+ LiveEntryNotFoundError
4
+ } from "astro/content/runtime";
5
+ import { PocketBaseAuthenticationError } from "../types/errors";
1
6
  import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
2
7
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
3
8
  import { pocketBaseEntry } from "../types/pocketbase-entry.type";
@@ -45,20 +50,30 @@ export async function fetchEntry<TEntry extends PocketBaseEntry>(
45
50
  options.superuserCredentials &&
46
51
  "impersonateToken" in options.superuserCredentials
47
52
  ) {
48
- 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
+ );
49
57
  } else {
50
- throw new Error(
58
+ throw new PocketBaseAuthenticationError(
59
+ options.collectionName,
51
60
  "The entry is not accessible without superuser rights. Please provide superuser credentials in the config."
52
61
  );
53
62
  }
54
63
  }
55
64
 
65
+ if (entryRequest.status === 404) {
66
+ throw new LiveEntryNotFoundError(options.collectionName, id);
67
+ }
68
+
56
69
  // Get the reason for the error
57
70
  const errorResponse = pocketBaseErrorResponse.parse(
58
71
  await entryRequest.json()
59
72
  );
60
- const errorMessage = `Fetching entry "${id}" from collection "${options.collectionName}" failed.\nReason: ${errorResponse.message}`;
61
- throw new Error(errorMessage);
73
+ throw new LiveCollectionError(
74
+ options.collectionName,
75
+ errorResponse.message
76
+ );
62
77
  }
63
78
 
64
79
  // Get the data from the response
@@ -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,7 +15,7 @@ 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 {
@@ -27,7 +28,23 @@ export async function liveCollectionLoader<TEntry extends PocketBaseEntry>(
27
28
  collectionFilter
28
29
  );
29
30
  } catch (error) {
30
- return { error: error instanceof Error ? error : new Error(String(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
+ };
31
48
  }
32
49
 
33
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 instanceof Error ? error : new Error(String(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";
@@ -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,7 +76,9 @@ 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
@@ -82,7 +86,7 @@ export function experimentalPocketbaseLiveLoader<
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
@@ -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
+ }