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 +11 -0
- package/package.json +6 -5
- package/src/index.ts +6 -1
- package/src/loader/cleanup-entries.ts +27 -6
- package/src/loader/fetch-collection.ts +33 -9
- package/src/loader/fetch-entry.ts +27 -8
- package/src/loader/live-collection-loader.ts +19 -3
- package/src/loader/live-entry-loader.ts +19 -2
- package/src/loader/parse-live-entry.ts +9 -10
- package/src/pocketbase-loader.ts +10 -6
- package/src/schema/get-remote-schema.ts +8 -3
- package/src/schema/parse-schema.ts +2 -0
- package/src/schema/read-local-schema.ts +4 -3
- package/src/schema/transform-files.ts +3 -2
- package/src/types/errors.ts +19 -0
- package/src/types/pocketbase-api-response.type.ts +40 -0
- package/src/types/pocketbase-entry.type.ts +19 -7
- package/src/types/pocketbase-schema.type.ts +47 -15
- package/src/utils/get-superuser-token.ts +10 -3
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.
|
|
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.
|
|
50
|
+
"astro": "5.15.1",
|
|
51
51
|
"globals": "16.4.0",
|
|
52
52
|
"husky": "9.1.7",
|
|
53
|
-
"lint-staged": "16.2.
|
|
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 {
|
|
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
|
|
71
|
-
.json()
|
|
72
|
-
|
|
73
|
-
const errorMessage = `Fetching ids failed with status code ${collectionRequest.status}.\nReason: ${
|
|
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 =
|
|
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
|
|
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
|
|
80
|
+
throw new PocketBaseAuthenticationError(
|
|
81
|
+
options.collectionName,
|
|
82
|
+
"The given impersonate token is not valid."
|
|
83
|
+
);
|
|
72
84
|
} else {
|
|
73
|
-
throw new
|
|
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
|
|
81
|
-
.json()
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 =
|
|
109
|
+
const response = pocketBaseListResponse.parse(
|
|
110
|
+
await collectionRequest.json()
|
|
111
|
+
);
|
|
89
112
|
|
|
90
113
|
// Return current chunk
|
|
91
|
-
|
|
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
|
|
53
|
+
throw new PocketBaseAuthenticationError(
|
|
54
|
+
options.collectionName,
|
|
55
|
+
"The given impersonate token is not valid."
|
|
56
|
+
);
|
|
47
57
|
} else {
|
|
48
|
-
throw new
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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) {
|
package/src/pocketbase-loader.ts
CHANGED
|
@@ -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
|
|
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<
|
|
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
|
|
85
|
+
return liveCollectionLoader<TEntry>(filter, options, token);
|
|
82
86
|
},
|
|
83
87
|
loadEntry: async ({
|
|
84
88
|
filter
|
|
85
|
-
}): Promise<LiveDataEntry<TEntry> | { 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
|
|
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
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
21
|
+
const fileContent = pocketBaseDatabase.safeParse(JSON.parse(schemaFile));
|
|
21
22
|
|
|
22
23
|
// Check if the database file is valid
|
|
23
|
-
if (!
|
|
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 =
|
|
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]
|
|
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]
|
|
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
|
|
4
|
+
* Base schema for all PocketBase entries.
|
|
3
5
|
*/
|
|
4
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
77
|
+
type: z.enum(["base", "view", "auth"]),
|
|
56
78
|
/**
|
|
57
79
|
* Schema of the collection.
|
|
58
80
|
*/
|
|
59
|
-
fields:
|
|
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
|
|
56
|
-
|
|
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
|
-
|
|
72
|
+
const response = pocketBaseLoginResponse.parse(await loginRequest.json());
|
|
73
|
+
return response.token;
|
|
67
74
|
}
|