astro-loader-pocketbase 2.8.2-next.3 → 2.8.2-next.4

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/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.8.2-next.4",
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",
@@ -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,7 @@
1
+ import {
2
+ pocketBaseErrorResponse,
3
+ pocketBaseListResponse
4
+ } from "../types/pocketbase-api-response.type";
1
5
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
2
6
  import type { ExperimentalPocketBaseLiveLoaderCollectionFilter } from "../types/pocketbase-live-loader-filter.type";
3
7
  import type { PocketBaseLoaderBaseOptions } from "../types/pocketbase-loader-options.type";
@@ -77,18 +81,21 @@ export async function fetchCollection<TEntry extends PocketBaseEntry>(
77
81
  }
78
82
 
79
83
  // 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
+ const errorResponse = pocketBaseErrorResponse.parse(
85
+ await collectionRequest.json()
86
+ );
87
+ const errorMessage = `Fetching data failed with status code ${collectionRequest.status}.\nReason: ${errorResponse.message}`;
84
88
  throw new Error(errorMessage);
85
89
  }
86
90
 
87
91
  // Get the data from the response
88
- const response = await collectionRequest.json();
92
+ const response = pocketBaseListResponse.parse(
93
+ await collectionRequest.json()
94
+ );
89
95
 
90
96
  // Return current chunk
91
- await chunkLoaded(response.items);
97
+ // oxlint-disable-next-line no-unsafe-type-assertion
98
+ await chunkLoaded(response.items as Array<TEntry>);
92
99
 
93
100
  // Update the page and total pages
94
101
  page = response.page;
@@ -1,4 +1,6 @@
1
+ import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
1
2
  import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
3
+ import { pocketBaseEntry } from "../types/pocketbase-entry.type";
2
4
  import type { ExperimentalPocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
3
5
  import { combineFieldsForRequest } from "../utils/combine-fields-for-request";
4
6
  import { formatFields } from "../utils/format-fields";
@@ -52,13 +54,15 @@ export async function fetchEntry<TEntry extends PocketBaseEntry>(
52
54
  }
53
55
 
54
56
  // 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
+ const errorResponse = pocketBaseErrorResponse.parse(
58
+ await entryRequest.json()
59
+ );
60
+ const errorMessage = `Fetching entry "${id}" from collection "${options.collectionName}" failed.\nReason: ${errorResponse.message}`;
57
61
  throw new Error(errorMessage);
58
62
  }
59
63
 
60
64
  // Get the data from the response
61
- const response = await entryRequest.json();
62
-
63
- return response;
65
+ const response = pocketBaseEntry.parse(await entryRequest.json());
66
+ // oxlint-disable-next-line no-unsafe-type-assertion
67
+ return response as TEntry;
64
68
  }
@@ -20,7 +20,6 @@ export async function liveCollectionLoader<TEntry extends PocketBaseEntry>(
20
20
  try {
21
21
  await fetchCollection<TEntry>(
22
22
  options,
23
- // oxlint-disable-next-line require-await
24
23
  async (chunk) => {
25
24
  entries.push(...chunk.map((entry) => parseLiveEntry(entry, options)));
26
25
  },
@@ -28,7 +27,7 @@ export async function liveCollectionLoader<TEntry extends PocketBaseEntry>(
28
27
  collectionFilter
29
28
  );
30
29
  } catch (error) {
31
- return { error: error as Error };
30
+ return { error: error instanceof Error ? error : new Error(String(error)) };
32
31
  }
33
32
 
34
33
  return {
@@ -16,6 +16,6 @@ export async function liveEntryLoader<TEntry extends PocketBaseEntry>(
16
16
  const entry = await fetchEntry<TEntry>(id, options, token);
17
17
  return parseLiveEntry(entry, options);
18
18
  } catch (error) {
19
- return { error: error as Error };
19
+ return { error: error instanceof Error ? error : new Error(String(error)) };
20
20
  }
21
21
  }
@@ -45,7 +45,7 @@ export function pocketbaseLoader(options: PocketBaseLoaderOptions): Loader {
45
45
  const token = await tokenPromise;
46
46
 
47
47
  // Generate the schema for the collection according to the API
48
- return await generateSchema(options, token);
48
+ return generateSchema(options, token);
49
49
  }
50
50
  };
51
51
  }
@@ -78,7 +78,7 @@ export function experimentalPocketbaseLiveLoader<
78
78
  const token = await tokenPromise;
79
79
 
80
80
  // Load entries from the collection
81
- return await liveCollectionLoader(filter, options, token);
81
+ return liveCollectionLoader<TEntry>(filter, options, token);
82
82
  },
83
83
  loadEntry: async ({
84
84
  filter
@@ -86,7 +86,7 @@ export function experimentalPocketbaseLiveLoader<
86
86
  const token = await tokenPromise;
87
87
 
88
88
  // Load a single entry from the collection
89
- return await liveEntryLoader(filter.id, options, token);
89
+ return liveEntryLoader<TEntry>(filter.id, options, token);
90
90
  }
91
91
  };
92
92
  }
@@ -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,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
  }