astro-loader-pocketbase 0.1.0 → 0.2.0
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
|
@@ -19,12 +19,36 @@ const blog = defineCollection({
|
|
|
19
19
|
});
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
By default, the loader will only fetch entries that have been modified since the last build.
|
|
23
|
+
Remember that due to the nature [Astros Content Layer lifecycle](https://astro.build/blog/content-layer-deep-dive#content-layer-lifecycle), the loader will **only fetch entries at build time**, even when using on-demand rendering.
|
|
24
|
+
If you want to update your deployed site with new entries, you need to rebuild it.
|
|
25
|
+
|
|
26
|
+
<sub>When running the dev server, you can trigger a reload by using `s + enter`.</sub>
|
|
27
|
+
|
|
22
28
|
### Type Generation
|
|
23
29
|
|
|
24
30
|
The loader can automatically generate types for your collection.
|
|
25
|
-
|
|
31
|
+
This is useful for type checking and autocompletion in your editor.
|
|
32
|
+
These types can be generated in two ways:
|
|
33
|
+
|
|
34
|
+
#### Remote Schema
|
|
35
|
+
|
|
36
|
+
To use the lice remote schema, you need to provide the email and password of an admin of the PocketBase instance.
|
|
26
37
|
Under the hood, the loader will use the [PocketBase API](https://pocketbase.io/docs/api-collections/#view-collection) to fetch the schema of your collection and generate types with Zod based on that schema.
|
|
27
38
|
|
|
39
|
+
#### Local Schema
|
|
40
|
+
|
|
41
|
+
If you don't want to provide the admin credentials (e.g. in a public repository), you can also provide the schema manually via a JSON file.
|
|
42
|
+
In PocketBase you can export the schema of the whole database to a `pb_schema.json` file.
|
|
43
|
+
If you provide the path to this file, the loader will use this schema to generate the types locally.
|
|
44
|
+
|
|
45
|
+
When admin credentials are provided, the loader will **always use the remote schema** since it's more up-to-date.
|
|
46
|
+
|
|
47
|
+
#### Manual Schema
|
|
48
|
+
|
|
49
|
+
If you don't want to use the automatic type generation, you can also [provide your own schema manually](https://5-0-0-beta.docs.astro.build/en/guides/content-collections/#defining-the-collection-schema).
|
|
50
|
+
This manual schema will **always override the automatic type generation**.
|
|
51
|
+
|
|
28
52
|
### Private Collections
|
|
29
53
|
|
|
30
54
|
If you want to access a private collection, you also need to provide the admin credentials.
|
|
@@ -39,4 +63,5 @@ Otherwise, you need to make the collection public in the PocketBase dashboard.
|
|
|
39
63
|
| `content` | `string \| Array<string>` | x | The field in the collection to use as content. This can also be an array of fields. |
|
|
40
64
|
| `adminEmail` | `string` | | The email of the admin of the PocketBase instance. This is used for automatic type generation and access to private collections. |
|
|
41
65
|
| `adminPassword` | `string` | | The password of the admin of the PocketBase instance. This is used for automatic type generation and access to private collections. |
|
|
66
|
+
| `localSchema` | `string` | | The path to a local schema file. This is used for automatic type generation. |
|
|
42
67
|
| `forceUpdate` | `boolean` | | If set to `true`, the loader will fetch every entry instead of only the ones modified since the last build. |
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "astro-loader-pocketbase",
|
|
3
3
|
"description": "A content loader for Astro that uses the PocketBase API",
|
|
4
4
|
"homepage": "https://github.com/pawcoding/astro-loader-pocketbase",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.ts"
|
|
@@ -25,14 +25,12 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"
|
|
28
|
+
"@types/node": "^22.5.5",
|
|
29
|
+
"astro": "^5.0.0-beta.1",
|
|
30
|
+
"typescript": "^5.6.2"
|
|
29
31
|
},
|
|
30
32
|
"peerDependencies": {
|
|
31
33
|
"astro": "^5.0.0"
|
|
32
34
|
},
|
|
33
|
-
"dependencies": {
|
|
34
|
-
"@astrojs/check": "^0.9.3",
|
|
35
|
-
"typescript": "^5.6.2"
|
|
36
|
-
},
|
|
37
35
|
"license": "MIT"
|
|
38
36
|
}
|
package/src/generate-schema.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ZodSchema } from "astro/zod";
|
|
2
2
|
import { z } from "astro/zod";
|
|
3
3
|
import type { PocketBaseLoaderOptions } from "./types/pocketbase-loader-options.type";
|
|
4
|
-
import {
|
|
4
|
+
import { getRemoteSchema } from "./utils/get-remote-schema";
|
|
5
5
|
import { parseSchema } from "./utils/parse-schema";
|
|
6
|
+
import { readLocalSchema } from "./utils/read-local-schema";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Basic schema for every PocketBase collection.
|
|
@@ -24,57 +25,26 @@ const BASIC_SCHEMA = {
|
|
|
24
25
|
export async function generateSchema(
|
|
25
26
|
options: PocketBaseLoaderOptions
|
|
26
27
|
): Promise<ZodSchema> {
|
|
27
|
-
|
|
28
|
+
let schema: Array<Record<string, any>> | undefined;
|
|
28
29
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
console.warn(
|
|
32
|
-
"Make sure to add an admin email and password to the config to get automatic type generation."
|
|
33
|
-
);
|
|
30
|
+
// Try to get the schema directly from the PocketBase instance
|
|
31
|
+
schema = await getRemoteSchema(options);
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// If the schema is not available, try to read it from a local schema file
|
|
34
|
+
if (!schema && options.localSchema) {
|
|
35
|
+
schema = await readLocalSchema(options.localSchema, options.collectionName);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
// If the token is invalid, return the basic schema
|
|
47
|
-
if (!token) {
|
|
48
|
-
return z.object(BASIC_SCHEMA);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Build URL and headers for the schema request
|
|
52
|
-
const schemaUrl = new URL(
|
|
53
|
-
`api/collections/${options.collectionName}`,
|
|
54
|
-
options.url
|
|
55
|
-
).href;
|
|
56
|
-
const schemaHeaders = new Headers();
|
|
57
|
-
schemaHeaders.set("Authorization", token);
|
|
58
|
-
|
|
59
|
-
// Fetch the schema
|
|
60
|
-
const schemaRequest = await fetch(schemaUrl, {
|
|
61
|
-
headers: schemaHeaders
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// If the request was not successful, return the basic schema
|
|
65
|
-
if (!schemaRequest.ok) {
|
|
66
|
-
const reason = await schemaRequest.json().then((data) => data.message);
|
|
67
|
-
const errorMessage = `Fetching schema from ${options.collectionName} failed with status code ${schemaRequest.status}.\nReason: ${reason}`;
|
|
68
|
-
console.error(errorMessage);
|
|
69
|
-
|
|
38
|
+
// If the schema is still not available, return the basic schema
|
|
39
|
+
if (!schema) {
|
|
40
|
+
console.error(
|
|
41
|
+
`No schema available for ${options.collectionName}. Only basic types are available. Please check your configuration and provide a valid schema file or admin credentials.`
|
|
42
|
+
);
|
|
70
43
|
return z.object(BASIC_SCHEMA);
|
|
71
44
|
}
|
|
72
45
|
|
|
73
|
-
// Get the schema from the response
|
|
74
|
-
const schema = await schemaRequest.json();
|
|
75
|
-
|
|
76
46
|
// Parse the schema
|
|
77
|
-
const fields = parseSchema(schema
|
|
47
|
+
const fields = parseSchema(schema);
|
|
78
48
|
|
|
79
49
|
// Check if the content field is present
|
|
80
50
|
if (typeof options.content === "string" && !fields[options.content]) {
|
|
@@ -30,6 +30,12 @@ export type PocketBaseLoaderOptions = {
|
|
|
30
30
|
* Together with `adminEmail` this is required to get automatic type generation and to access all resources even if they are not public.
|
|
31
31
|
*/
|
|
32
32
|
adminPassword?: string;
|
|
33
|
+
/**
|
|
34
|
+
* File path to the local schema file.
|
|
35
|
+
* This file will be used to generate the schema for the collection.
|
|
36
|
+
* If admin credentials are provided (see `adminEmail` and `adminPassword`), this option will be ignored.
|
|
37
|
+
*/
|
|
38
|
+
localSchema?: string;
|
|
33
39
|
/**
|
|
34
40
|
* By default, the loader will only fetch entries that have been modified since the last fetch.
|
|
35
41
|
* If you want to fetch all entries, set this to `true`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
2
|
+
import { getAdminToken } from "./get-admin-token";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fetches the schema for the specified collection from the PocketBase instance.
|
|
6
|
+
*
|
|
7
|
+
* @param options Options for the loader. See {@link PocketBaseLoaderOptions} for more details.
|
|
8
|
+
*/
|
|
9
|
+
export async function getRemoteSchema(
|
|
10
|
+
options: PocketBaseLoaderOptions
|
|
11
|
+
): Promise<Array<Record<string, any>> | undefined> {
|
|
12
|
+
if (!options.adminEmail || !options.adminPassword) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Get the admin token
|
|
17
|
+
const token = await getAdminToken(
|
|
18
|
+
options.url,
|
|
19
|
+
options.adminEmail,
|
|
20
|
+
options.adminPassword
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// If the token is invalid, return the basic schema
|
|
24
|
+
if (!token) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Build URL and headers for the schema request
|
|
29
|
+
const schemaUrl = new URL(
|
|
30
|
+
`api/collections/${options.collectionName}`,
|
|
31
|
+
options.url
|
|
32
|
+
).href;
|
|
33
|
+
const schemaHeaders = new Headers();
|
|
34
|
+
schemaHeaders.set("Authorization", token);
|
|
35
|
+
|
|
36
|
+
// Fetch the schema
|
|
37
|
+
const schemaRequest = await fetch(schemaUrl, {
|
|
38
|
+
headers: schemaHeaders
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// If the request was not successful, return the basic schema
|
|
42
|
+
if (!schemaRequest.ok) {
|
|
43
|
+
const reason = await schemaRequest.json().then((data) => data.message);
|
|
44
|
+
const errorMessage = `Fetching schema from ${options.collectionName} failed with status code ${schemaRequest.status}.\nReason: ${reason}`;
|
|
45
|
+
console.error(errorMessage);
|
|
46
|
+
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Get the schema from the response
|
|
51
|
+
const schema = await schemaRequest.json();
|
|
52
|
+
return schema.schema;
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads the local PocketBase schema file and returns the schema for the specified collection.
|
|
6
|
+
*
|
|
7
|
+
* @param localSchemaPath Path to the local schema file.
|
|
8
|
+
* @param collectionName Name of the collection to get the schema for.
|
|
9
|
+
*/
|
|
10
|
+
export async function readLocalSchema(
|
|
11
|
+
localSchemaPath: string,
|
|
12
|
+
collectionName: string
|
|
13
|
+
): Promise<Array<Record<string, any>> | undefined> {
|
|
14
|
+
const realPath = path.join(process.cwd(), localSchemaPath);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Read the schema file
|
|
18
|
+
const schemaFile = await fs.readFile(realPath, "utf-8");
|
|
19
|
+
const database = JSON.parse(schemaFile);
|
|
20
|
+
|
|
21
|
+
// Check if the database file is valid
|
|
22
|
+
if (!database || !Array.isArray(database)) {
|
|
23
|
+
throw new Error("Invalid schema file");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find and return the schema for the collection
|
|
27
|
+
const schema = database.find(
|
|
28
|
+
(collection) => collection.name === collectionName
|
|
29
|
+
);
|
|
30
|
+
return schema.schema;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(
|
|
33
|
+
`Failed to read local schema from ${localSchemaPath}. No types will be generated.\nReason: ${error}`
|
|
34
|
+
);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|