astro-loader-pocketbase 0.2.0 → 0.2.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/LICENSE +21 -0
- package/README.md +7 -1
- package/package.json +25 -20
- package/src/generate-schema.ts +2 -1
- package/src/types/pocketbase-base.type.ts +30 -0
- package/src/types/pocketbase-loader-options.type.ts +2 -2
- package/src/types/pocketbase-schema.type.ts +46 -0
- package/src/utils/get-remote-schema.ts +6 -2
- package/src/utils/parse-entry.ts +3 -2
- package/src/utils/parse-schema.ts +20 -9
- package/src/utils/read-local-schema.ts +13 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 pawcode Development
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
# astro-pocketbase
|
|
1
|
+
# astro-loader-pocketbase
|
|
2
|
+
|
|
3
|
+
<!--  -->
|
|
4
|
+
[](https://www.npmjs.com/package/astro-loader-pocketbase)
|
|
5
|
+
[](https://www.npmjs.com/package/astro-loader-pocketbase)
|
|
6
|
+
[](https://github.com/pawcoding/astro-loader-pocketbase/blob/master/LICENSE)
|
|
7
|
+
[](https://discord.gg/GzgTh4hxrx)
|
|
2
8
|
|
|
3
9
|
This package is a simple loader to load data from a PocketBase database into Astro using the [Astro Loader API](https://5-0-0-beta.docs.astro.build/en/reference/loader-reference/) introduced in Astro 5.
|
|
4
10
|
|
package/package.json
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-loader-pocketbase",
|
|
3
|
+
"version": "0.2.2",
|
|
3
4
|
"description": "A content loader for Astro that uses the PocketBase API",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Luis Wolf <development@pawcode.de> (https://pawcode.de)",
|
|
4
7
|
"homepage": "https://github.com/pawcoding/astro-loader-pocketbase",
|
|
5
|
-
"version": "0.2.0",
|
|
6
8
|
"type": "module",
|
|
7
9
|
"exports": {
|
|
8
10
|
".": "./index.ts"
|
|
9
11
|
},
|
|
10
12
|
"files": [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
+
"index.ts",
|
|
14
|
+
"src"
|
|
13
15
|
],
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
16
|
+
"scripts": {
|
|
17
|
+
"lint": "npx eslint",
|
|
18
|
+
"prepare": "husky"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"astro": "^5.0.0"
|
|
18
22
|
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"withastro",
|
|
21
|
-
"astro",
|
|
22
|
-
"astro-loader",
|
|
23
|
-
"astro-content-loader",
|
|
24
|
-
"pocketbase"
|
|
25
|
-
],
|
|
26
|
-
"scripts": {},
|
|
27
23
|
"devDependencies": {
|
|
24
|
+
"@eslint/js": "^9.11.1",
|
|
25
|
+
"@stylistic/eslint-plugin": "^2.8.0",
|
|
28
26
|
"@types/node": "^22.5.5",
|
|
29
27
|
"astro": "^5.0.0-beta.1",
|
|
30
|
-
"
|
|
28
|
+
"eslint": "^9.11.1",
|
|
29
|
+
"globals": "^15.9.0",
|
|
30
|
+
"husky": "^9.1.6",
|
|
31
|
+
"typescript": "^5.6.2",
|
|
32
|
+
"typescript-eslint": "^8.7.0"
|
|
31
33
|
},
|
|
32
|
-
"
|
|
33
|
-
"astro"
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
"keywords": [
|
|
35
|
+
"astro",
|
|
36
|
+
"astro-content-loader",
|
|
37
|
+
"astro-loader",
|
|
38
|
+
"pocketbase",
|
|
39
|
+
"withastro"
|
|
40
|
+
]
|
|
36
41
|
}
|
package/src/generate-schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 type { PocketBaseSchemaEntry } from "./types/pocketbase-schema.type";
|
|
4
5
|
import { getRemoteSchema } from "./utils/get-remote-schema";
|
|
5
6
|
import { parseSchema } from "./utils/parse-schema";
|
|
6
7
|
import { readLocalSchema } from "./utils/read-local-schema";
|
|
@@ -25,7 +26,7 @@ const BASIC_SCHEMA = {
|
|
|
25
26
|
export async function generateSchema(
|
|
26
27
|
options: PocketBaseLoaderOptions
|
|
27
28
|
): Promise<ZodSchema> {
|
|
28
|
-
let schema: Array<
|
|
29
|
+
let schema: Array<PocketBaseSchemaEntry> | undefined;
|
|
29
30
|
|
|
30
31
|
// Try to get the schema directly from the PocketBase instance
|
|
31
32
|
schema = await getRemoteSchema(options);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for all PocketBase entries.
|
|
3
|
+
*/
|
|
4
|
+
interface PocketBaseBaseEntry {
|
|
5
|
+
/**
|
|
6
|
+
* ID of the entry.
|
|
7
|
+
*/
|
|
8
|
+
id: string;
|
|
9
|
+
/**
|
|
10
|
+
* ID of the collection the entry belongs to.
|
|
11
|
+
*/
|
|
12
|
+
collectionId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Name of the collection the entry belongs to.
|
|
15
|
+
*/
|
|
16
|
+
collectionName: string;
|
|
17
|
+
/**
|
|
18
|
+
* Date the entry was created.
|
|
19
|
+
*/
|
|
20
|
+
created: string;
|
|
21
|
+
/**
|
|
22
|
+
* Date the entry was last updated.
|
|
23
|
+
*/
|
|
24
|
+
updated: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Type for a PocketBase entry.
|
|
29
|
+
*/
|
|
30
|
+
export type PocketBaseEntry = PocketBaseBaseEntry & Record<string, unknown>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Options for the PocketBase loader.
|
|
3
3
|
*/
|
|
4
|
-
export
|
|
4
|
+
export interface PocketBaseLoaderOptions {
|
|
5
5
|
/**
|
|
6
6
|
* URL of the PocketBase instance.
|
|
7
7
|
*/
|
|
@@ -41,4 +41,4 @@ export type PocketBaseLoaderOptions = {
|
|
|
41
41
|
* If you want to fetch all entries, set this to `true`.
|
|
42
42
|
*/
|
|
43
43
|
forceUpdate?: boolean;
|
|
44
|
-
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry for a collections schema in PocketBase.
|
|
3
|
+
*/
|
|
4
|
+
export interface PocketBaseSchemaEntry {
|
|
5
|
+
/**
|
|
6
|
+
* Name of the field.
|
|
7
|
+
*/
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* Type of the field.
|
|
11
|
+
*/
|
|
12
|
+
type: string;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the field is required.
|
|
15
|
+
*/
|
|
16
|
+
required: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Options for the field.
|
|
19
|
+
*/
|
|
20
|
+
options: {
|
|
21
|
+
/**
|
|
22
|
+
* Values for a select field.
|
|
23
|
+
* This is only present if the field type is "select".
|
|
24
|
+
*/
|
|
25
|
+
values?: Array<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum number of values for a select field.
|
|
28
|
+
* This is only present on "select", "relation", and "file" fields.
|
|
29
|
+
*/
|
|
30
|
+
maxSelect?: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Schema for a PocketBase collection.
|
|
36
|
+
*/
|
|
37
|
+
export interface PocketBaseCollection {
|
|
38
|
+
/**
|
|
39
|
+
* Name of the collection.
|
|
40
|
+
*/
|
|
41
|
+
name: string;
|
|
42
|
+
/**
|
|
43
|
+
* Schema of the collection.
|
|
44
|
+
*/
|
|
45
|
+
schema: Array<PocketBaseSchemaEntry>;
|
|
46
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
2
|
+
import type {
|
|
3
|
+
PocketBaseCollection,
|
|
4
|
+
PocketBaseSchemaEntry
|
|
5
|
+
} from "../types/pocketbase-schema.type";
|
|
2
6
|
import { getAdminToken } from "./get-admin-token";
|
|
3
7
|
|
|
4
8
|
/**
|
|
@@ -8,7 +12,7 @@ import { getAdminToken } from "./get-admin-token";
|
|
|
8
12
|
*/
|
|
9
13
|
export async function getRemoteSchema(
|
|
10
14
|
options: PocketBaseLoaderOptions
|
|
11
|
-
): Promise<Array<
|
|
15
|
+
): Promise<Array<PocketBaseSchemaEntry> | undefined> {
|
|
12
16
|
if (!options.adminEmail || !options.adminPassword) {
|
|
13
17
|
return undefined;
|
|
14
18
|
}
|
|
@@ -48,6 +52,6 @@ export async function getRemoteSchema(
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
// Get the schema from the response
|
|
51
|
-
const schema = await schemaRequest.json();
|
|
55
|
+
const schema: PocketBaseCollection = await schemaRequest.json();
|
|
52
56
|
return schema.schema;
|
|
53
57
|
}
|
package/src/utils/parse-entry.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LoaderContext } from "astro/loaders";
|
|
2
|
+
import type { PocketBaseEntry } from "../types/pocketbase-base.type";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Parse an entry from PocketBase to match the schema and store it in the store.
|
|
@@ -9,7 +10,7 @@ import type { LoaderContext } from "astro/loaders";
|
|
|
9
10
|
* If multiple fields are used, they will be concatenated and wrapped in `<section>` elements.
|
|
10
11
|
*/
|
|
11
12
|
export async function parseEntry(
|
|
12
|
-
entry:
|
|
13
|
+
entry: PocketBaseEntry,
|
|
13
14
|
{ generateDigest, parseData, store }: LoaderContext,
|
|
14
15
|
contentFields: string | Array<string>
|
|
15
16
|
): Promise<void> {
|
|
@@ -28,7 +29,7 @@ export async function parseEntry(
|
|
|
28
29
|
let content: string;
|
|
29
30
|
if (typeof contentFields === "string") {
|
|
30
31
|
// Only one field is used as content
|
|
31
|
-
content = entry[contentFields]
|
|
32
|
+
content = `${entry[contentFields]}`;
|
|
32
33
|
} else {
|
|
33
34
|
// Multiple fields are used as content, wrap each block in a section and concatenate them
|
|
34
35
|
content = contentFields
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "astro/zod";
|
|
2
|
+
import type { PocketBaseSchemaEntry } from "../types/pocketbase-schema.type";
|
|
2
3
|
|
|
3
4
|
export function parseSchema(
|
|
4
|
-
schema: Array<
|
|
5
|
+
schema: Array<PocketBaseSchemaEntry>
|
|
5
6
|
): Record<string, z.ZodType> {
|
|
6
7
|
// Prepare the schemas fields
|
|
7
8
|
const fields: Record<string, z.ZodType> = {};
|
|
@@ -14,29 +15,36 @@ export function parseSchema(
|
|
|
14
15
|
switch (field.type) {
|
|
15
16
|
case "number":
|
|
16
17
|
// Coerce the value to a number
|
|
17
|
-
fieldType = z.number(
|
|
18
|
+
fieldType = z.coerce.number();
|
|
18
19
|
break;
|
|
19
20
|
case "bool":
|
|
20
21
|
// Coerce the value to a boolean
|
|
21
|
-
fieldType = z.boolean(
|
|
22
|
+
fieldType = z.coerce.boolean();
|
|
22
23
|
break;
|
|
23
24
|
case "date":
|
|
24
25
|
// Coerce and parse the value as a date
|
|
25
|
-
fieldType = z.date(
|
|
26
|
+
fieldType = z.coerce.date();
|
|
26
27
|
break;
|
|
27
28
|
case "select":
|
|
29
|
+
if (!field.options.values) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Field ${field.name} is of type "select" but has no values defined.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
// Create an enum for the select values
|
|
36
|
+
// @ts-expect-error - Zod complains because the values are not known at compile time and thus the array is not static.
|
|
29
37
|
const values = z.enum(field.options.values);
|
|
30
38
|
|
|
31
39
|
// Parse the field type based on the number of values it can have
|
|
32
|
-
fieldType = parseSingleOrMultipleValues(field
|
|
40
|
+
fieldType = parseSingleOrMultipleValues(field, values);
|
|
33
41
|
break;
|
|
34
42
|
case "relation":
|
|
35
43
|
case "file":
|
|
36
44
|
// NOTE: Relations and files are currently not supported and are treated as strings
|
|
37
45
|
|
|
38
46
|
// Parse the field type based on the number of values it can have
|
|
39
|
-
fieldType = parseSingleOrMultipleValues(field
|
|
47
|
+
fieldType = parseSingleOrMultipleValues(field, z.string());
|
|
40
48
|
break;
|
|
41
49
|
case "json":
|
|
42
50
|
// Parse the field as an object
|
|
@@ -50,7 +58,10 @@ export function parseSchema(
|
|
|
50
58
|
|
|
51
59
|
// If the field is not required, mark it as optional
|
|
52
60
|
if (!field.required) {
|
|
53
|
-
fieldType.
|
|
61
|
+
fieldType = z.preprocess(
|
|
62
|
+
(val) => val || undefined,
|
|
63
|
+
z.optional(fieldType)
|
|
64
|
+
);
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
// Add the field to the fields object
|
|
@@ -69,11 +80,11 @@ export function parseSchema(
|
|
|
69
80
|
* @returns The parsed field type
|
|
70
81
|
*/
|
|
71
82
|
function parseSingleOrMultipleValues(
|
|
72
|
-
field:
|
|
83
|
+
field: PocketBaseSchemaEntry,
|
|
73
84
|
type: z.ZodType
|
|
74
85
|
) {
|
|
75
86
|
// If the select allows multiple values, create an array of the enum
|
|
76
|
-
if (field.options.maxSelect === 1) {
|
|
87
|
+
if (field.options.maxSelect === undefined || field.options.maxSelect === 1) {
|
|
77
88
|
return type;
|
|
78
89
|
} else {
|
|
79
90
|
return z.array(type);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import type {
|
|
4
|
+
PocketBaseCollection,
|
|
5
|
+
PocketBaseSchemaEntry
|
|
6
|
+
} from "../types/pocketbase-schema.type";
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Reads the local PocketBase schema file and returns the schema for the specified collection.
|
|
@@ -10,13 +14,13 @@ import path from "path";
|
|
|
10
14
|
export async function readLocalSchema(
|
|
11
15
|
localSchemaPath: string,
|
|
12
16
|
collectionName: string
|
|
13
|
-
): Promise<Array<
|
|
17
|
+
): Promise<Array<PocketBaseSchemaEntry> | undefined> {
|
|
14
18
|
const realPath = path.join(process.cwd(), localSchemaPath);
|
|
15
19
|
|
|
16
20
|
try {
|
|
17
21
|
// Read the schema file
|
|
18
22
|
const schemaFile = await fs.readFile(realPath, "utf-8");
|
|
19
|
-
const database = JSON.parse(schemaFile);
|
|
23
|
+
const database: Array<PocketBaseCollection> = JSON.parse(schemaFile);
|
|
20
24
|
|
|
21
25
|
// Check if the database file is valid
|
|
22
26
|
if (!database || !Array.isArray(database)) {
|
|
@@ -27,6 +31,13 @@ export async function readLocalSchema(
|
|
|
27
31
|
const schema = database.find(
|
|
28
32
|
(collection) => collection.name === collectionName
|
|
29
33
|
);
|
|
34
|
+
|
|
35
|
+
if (!schema) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Collection "${collectionName}" not found in schema file`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
return schema.schema;
|
|
31
42
|
} catch (error) {
|
|
32
43
|
console.error(
|