astro-loader-pocketbase 0.6.0-pocketbase-rc.3 → 1.0.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 +34 -50
- package/package.json +4 -4
- package/src/cleanup-entries.ts +9 -9
- package/src/generate-schema.ts +34 -50
- package/src/load-entries.ts +30 -20
- package/src/pocketbase-loader.ts +41 -23
- package/src/types/pocketbase-entry.type.ts +8 -0
- package/src/types/pocketbase-loader-options.type.ts +16 -21
- package/src/types/pocketbase-schema.type.ts +16 -26
- package/src/utils/{get-superuser-token.ts → get-admin-token.ts} +10 -14
- package/src/utils/get-remote-schema.ts +8 -7
- package/src/utils/parse-entry.ts +6 -10
- package/src/utils/parse-schema.ts +6 -17
- package/src/utils/transform-files.ts +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# astro-loader-pocketbase
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+

|
|
5
4
|
[](https://www.npmjs.com/package/astro-loader-pocketbase)
|
|
6
5
|
[](https://www.npmjs.com/package/astro-loader-pocketbase)
|
|
7
6
|
[](https://github.com/pawcoding/astro-loader-pocketbase/blob/master/LICENSE)
|
|
@@ -9,20 +8,14 @@
|
|
|
9
8
|
|
|
10
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.
|
|
11
10
|
|
|
12
|
-
> [!WARNING]
|
|
13
|
-
> This package is still under development.
|
|
14
|
-
> It will have a first stable release when Astro 5 is released.
|
|
15
|
-
> Until then, **breaking changes can occur at any time**.
|
|
16
|
-
|
|
17
11
|
> [!TIP]
|
|
18
12
|
> If you want to see the PocketBase data directly in your Astro toolbar, try the [`astro-integration-pocketbase`](https://github.com/pawcoding/astro-integration-pocketbase).
|
|
19
13
|
|
|
20
14
|
## Compatibility
|
|
21
15
|
|
|
22
|
-
| Loader
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
| <= [0.5.0](https://github.com/pawcoding/astro-loader-pocketbase/tree/v0.5.0) | >= 5.0.0-beta | < 0.23.0 |
|
|
16
|
+
| Loader | Astro | PocketBase |
|
|
17
|
+
| ------ | ----- | ---------- |
|
|
18
|
+
| 1.0.0 | 5.0.0 | <= 0.22.0 |
|
|
26
19
|
|
|
27
20
|
## Basic usage
|
|
28
21
|
|
|
@@ -42,30 +35,12 @@ const blog = defineCollection({
|
|
|
42
35
|
export const collections = { blog };
|
|
43
36
|
```
|
|
44
37
|
|
|
38
|
+
By default, the loader will only fetch entries that have been modified since the last build.
|
|
45
39
|
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.
|
|
46
40
|
If you want to update your deployed site with new entries, you need to rebuild it.
|
|
47
41
|
|
|
48
42
|
<sub>When running the dev server, you can trigger a reload by using `s + enter`.</sub>
|
|
49
43
|
|
|
50
|
-
## Incremental builds
|
|
51
|
-
|
|
52
|
-
Since PocketBase 0.23.0, the `updated` field is not mandatory anymore.
|
|
53
|
-
This means that the loader can't automatically detect when an entry has been modified.
|
|
54
|
-
To enable incremental builds, you need to provide the name of a field in your collection that stores the last update date of an entry.
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
const blog = defineCollection({
|
|
58
|
-
loader: pocketbaseLoader({
|
|
59
|
-
...options,
|
|
60
|
-
updatedField: "<field-in-collection>"
|
|
61
|
-
})
|
|
62
|
-
});
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
When this field is provided, the loader will only fetch entries that have been modified since the last build.
|
|
66
|
-
Ideally, this field should be of type `autodate` and have the value "Update" or "Create/Update" in the PocketBase dashboard.
|
|
67
|
-
This ensures that the field is automatically updated when an entry is modified.
|
|
68
|
-
|
|
69
44
|
## Entries
|
|
70
45
|
|
|
71
46
|
After generating the schema (see below), the loader will automatically parse the content of the entries (e.g. transform ISO dates to `Date` objects, coerce numbers, etc.).
|
|
@@ -79,7 +54,7 @@ This content will then be used when calling the `render` function of [Astros con
|
|
|
79
54
|
const blog = defineCollection({
|
|
80
55
|
loader: pocketbaseLoader({
|
|
81
56
|
...options,
|
|
82
|
-
|
|
57
|
+
content: "<field-in-collection>"
|
|
83
58
|
})
|
|
84
59
|
});
|
|
85
60
|
```
|
|
@@ -122,16 +97,14 @@ These types can be generated in two ways:
|
|
|
122
97
|
|
|
123
98
|
### Remote schema
|
|
124
99
|
|
|
125
|
-
To use the
|
|
100
|
+
To use the lice remote schema, you need to provide the email and password of an admin of the PocketBase instance.
|
|
126
101
|
|
|
127
102
|
```ts
|
|
128
103
|
const blog = defineCollection({
|
|
129
104
|
loader: pocketbaseLoader({
|
|
130
105
|
...options,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
password: "<superuser-password>"
|
|
134
|
-
}
|
|
106
|
+
adminEmail: "<admin-email>",
|
|
107
|
+
adminPassword: "<admin-password>"
|
|
135
108
|
})
|
|
136
109
|
});
|
|
137
110
|
```
|
|
@@ -140,7 +113,7 @@ Under the hood, the loader will use the [PocketBase API](https://pocketbase.io/d
|
|
|
140
113
|
|
|
141
114
|
### Local schema
|
|
142
115
|
|
|
143
|
-
If you don't want to provide
|
|
116
|
+
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.
|
|
144
117
|
|
|
145
118
|
```ts
|
|
146
119
|
const blog = defineCollection({
|
|
@@ -154,7 +127,7 @@ const blog = defineCollection({
|
|
|
154
127
|
In PocketBase you can export the schema of the whole database to a `pb_schema.json` file.
|
|
155
128
|
If you provide the path to this file, the loader will use this schema to generate the types locally.
|
|
156
129
|
|
|
157
|
-
When
|
|
130
|
+
When admin credentials are provided, the loader will **always use the remote schema** since it's more up-to-date.
|
|
158
131
|
|
|
159
132
|
### Manual schema
|
|
160
133
|
|
|
@@ -163,26 +136,37 @@ This manual schema will **always override the automatic type generation**.
|
|
|
163
136
|
|
|
164
137
|
## All options
|
|
165
138
|
|
|
166
|
-
| Option
|
|
167
|
-
|
|
|
168
|
-
| `url`
|
|
169
|
-
| `collectionName`
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `localSchema`
|
|
175
|
-
| `jsonSchemas`
|
|
139
|
+
| Option | Type | Required | Description |
|
|
140
|
+
| ---------------- | ----------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `url` | `string` | x | The URL of your PocketBase instance. |
|
|
142
|
+
| `collectionName` | `string` | x | The name of the collection in your PocketBase instance. |
|
|
143
|
+
| `content` | `string \| Array<string>` | | The field in the collection to use as content. This can also be an array of fields. |
|
|
144
|
+
| `adminEmail` | `string` | | The email of the admin of the PocketBase instance. This is used for automatic type generation and access to private collections. |
|
|
145
|
+
| `adminPassword` | `string` | | The password of the admin of the PocketBase instance. This is used for automatic type generation and access to private collections. |
|
|
146
|
+
| `id` | `string` | | The field in the collection to use as unique id. Defaults to `id`. |
|
|
147
|
+
| `localSchema` | `string` | | The path to a local schema file. This is used for automatic type generation. |
|
|
148
|
+
| `jsonSchemas` | `Record<string, z.ZodSchema>` | | A record of Zod schemas to use for type generation of `json` fields. |
|
|
149
|
+
| `forceUpdate` | `boolean` | | If set to `true`, the loader will fetch every entry instead of only the ones modified since the last build. |
|
|
176
150
|
|
|
177
151
|
## Special cases
|
|
178
152
|
|
|
179
|
-
### Private collections
|
|
153
|
+
### Private collections
|
|
180
154
|
|
|
181
|
-
If you want to access a private collection
|
|
155
|
+
If you want to access a private collection, you also need to provide the admin credentials.
|
|
182
156
|
Otherwise, you need to make the collection public in the PocketBase dashboard.
|
|
183
157
|
|
|
184
158
|
Generally, it's not recommended to use private collections, especially when users should be able to see images or other files stored in the collection.
|
|
185
159
|
|
|
160
|
+
### View collections
|
|
161
|
+
|
|
162
|
+
Out of the box, the loader also supports collections with the type `view`, though with some limitations.
|
|
163
|
+
To enable incremental builds, the loader needs to know when an entry has been modified.
|
|
164
|
+
Normal `base` collections have a `updated` field that is automatically updated when an entry is modified.
|
|
165
|
+
Thus, `view` collections that don't include this field can't be incrementally built but will be fetched every time.
|
|
166
|
+
|
|
167
|
+
You can also alias another field as `updated` (as long as it's a date field) in your view.
|
|
168
|
+
While this is possible, it's not recommended since it can lead to outdated data not being fetched.
|
|
169
|
+
|
|
186
170
|
### JSON fields
|
|
187
171
|
|
|
188
172
|
PocketBase can store arbitrary JSON data in a `json` field.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-loader-pocketbase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A content loader for Astro that uses the PocketBase API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Luis Wolf <development@pawcode.de> (https://pawcode.de)",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"@eslint/js": "^9.16.0",
|
|
25
25
|
"@stylistic/eslint-plugin": "^2.11.0",
|
|
26
26
|
"@types/node": "^22.10.1",
|
|
27
|
-
"astro": "^5.0.
|
|
27
|
+
"astro": "^5.0.3",
|
|
28
28
|
"eslint": "^9.16.0",
|
|
29
|
-
"globals": "^15.
|
|
29
|
+
"globals": "^15.13.0",
|
|
30
30
|
"husky": "^9.1.7",
|
|
31
31
|
"typescript": "^5.7.2",
|
|
32
|
-
"typescript-eslint": "^8.
|
|
32
|
+
"typescript-eslint": "^8.17.0"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"astro",
|
package/src/cleanup-entries.ts
CHANGED
|
@@ -6,12 +6,12 @@ import type { PocketBaseLoaderOptions } from "./types/pocketbase-loader-options.
|
|
|
6
6
|
*
|
|
7
7
|
* @param options Options for the loader.
|
|
8
8
|
* @param context Context of the loader.
|
|
9
|
-
* @param
|
|
9
|
+
* @param adminToken Admin token to access all resources.
|
|
10
10
|
*/
|
|
11
11
|
export async function cleanupEntries(
|
|
12
12
|
options: PocketBaseLoaderOptions,
|
|
13
13
|
context: LoaderContext,
|
|
14
|
-
|
|
14
|
+
adminToken: string | undefined
|
|
15
15
|
): Promise<void> {
|
|
16
16
|
// Build the URL for the collections endpoint
|
|
17
17
|
const collectionUrl = new URL(
|
|
@@ -19,10 +19,10 @@ export async function cleanupEntries(
|
|
|
19
19
|
options.url
|
|
20
20
|
).href;
|
|
21
21
|
|
|
22
|
-
// Create the headers for the request to append the
|
|
22
|
+
// Create the headers for the request to append the admin token (if available)
|
|
23
23
|
const collectionHeaders = new Headers();
|
|
24
|
-
if (
|
|
25
|
-
collectionHeaders.set("Authorization",
|
|
24
|
+
if (adminToken) {
|
|
25
|
+
collectionHeaders.set("Authorization", adminToken);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Prepare pagination variables
|
|
@@ -42,10 +42,10 @@ export async function cleanupEntries(
|
|
|
42
42
|
|
|
43
43
|
// If the request was not successful, print the error message and return
|
|
44
44
|
if (!collectionRequest.ok) {
|
|
45
|
-
// If the collection is locked, an
|
|
45
|
+
// If the collection is locked, an admin token is required
|
|
46
46
|
if (collectionRequest.status === 403) {
|
|
47
47
|
context.logger.error(
|
|
48
|
-
`
|
|
48
|
+
`The collection ${options.collectionName} is not accessible without an admin rights. Please provide an admin email and password in the config.`
|
|
49
49
|
);
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
@@ -53,7 +53,7 @@ export async function cleanupEntries(
|
|
|
53
53
|
const reason = await collectionRequest
|
|
54
54
|
.json()
|
|
55
55
|
.then((data) => data.message);
|
|
56
|
-
const errorMessage = `
|
|
56
|
+
const errorMessage = `Fetching ids from ${options.collectionName} failed with status code ${collectionRequest.status}.\nReason: ${reason}`;
|
|
57
57
|
context.logger.error(errorMessage);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
@@ -86,7 +86,7 @@ export async function cleanupEntries(
|
|
|
86
86
|
if (cleanedUp > 0) {
|
|
87
87
|
// Log the number of cleaned up entries
|
|
88
88
|
context.logger.info(
|
|
89
|
-
`
|
|
89
|
+
`Cleaned up ${cleanedUp} old entries for ${context.collection}`
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
}
|
package/src/generate-schema.ts
CHANGED
|
@@ -13,7 +13,20 @@ import { transformFiles } from "./utils/transform-files";
|
|
|
13
13
|
const BASIC_SCHEMA = {
|
|
14
14
|
id: z.string(),
|
|
15
15
|
collectionId: z.string().length(15),
|
|
16
|
-
collectionName: z.string()
|
|
16
|
+
collectionName: z.string(),
|
|
17
|
+
created: z.coerce.date(),
|
|
18
|
+
updated: z.coerce.date()
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Basic schema for a view in PocketBase.
|
|
23
|
+
*/
|
|
24
|
+
const VIEW_SCHEMA = {
|
|
25
|
+
id: z.string(),
|
|
26
|
+
collectionId: z.string().length(15),
|
|
27
|
+
collectionName: z.string(),
|
|
28
|
+
created: z.preprocess((val) => val || undefined, z.optional(z.coerce.date())),
|
|
29
|
+
updated: z.preprocess((val) => val || undefined, z.optional(z.coerce.date()))
|
|
17
30
|
};
|
|
18
31
|
|
|
19
32
|
/**
|
|
@@ -24,7 +37,7 @@ const VALID_ID_TYPES = ["text", "number", "email", "url", "date"];
|
|
|
24
37
|
/**
|
|
25
38
|
* Generate a schema for the collection based on the collection's schema in PocketBase.
|
|
26
39
|
* By default, a basic schema is returned if no other schema is available.
|
|
27
|
-
* If
|
|
40
|
+
* If admin credentials are provided, the schema is fetched from the PocketBase API.
|
|
28
41
|
* If a path to a local schema file is provided, the schema is read from the file.
|
|
29
42
|
*
|
|
30
43
|
* @param options Options for the loader. See {@link PocketBaseLoaderOptions} for more details.
|
|
@@ -37,8 +50,6 @@ export async function generateSchema(
|
|
|
37
50
|
// Try to get the schema directly from the PocketBase instance
|
|
38
51
|
collection = await getRemoteSchema(options);
|
|
39
52
|
|
|
40
|
-
const hasSuperuserRights = !!collection || !!options.superuserCredentials;
|
|
41
|
-
|
|
42
53
|
// If the schema is not available, try to read it from a local schema file
|
|
43
54
|
if (!collection && options.localSchema) {
|
|
44
55
|
collection = await readLocalSchema(
|
|
@@ -50,34 +61,30 @@ export async function generateSchema(
|
|
|
50
61
|
// If the schema is still not available, return the basic schema
|
|
51
62
|
if (!collection) {
|
|
52
63
|
console.error(
|
|
53
|
-
`No schema available for
|
|
64
|
+
`No schema available for ${options.collectionName}. Only basic types are available. Please check your configuration and provide a valid schema file or admin credentials.`
|
|
54
65
|
);
|
|
55
|
-
// Return the
|
|
56
|
-
return z.object(
|
|
66
|
+
// Return the view schema since every collection has at least the view schema
|
|
67
|
+
return z.object(VIEW_SCHEMA);
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
// Parse the schema
|
|
60
|
-
const fields = parseSchema(
|
|
61
|
-
collection,
|
|
62
|
-
options.jsonSchemas,
|
|
63
|
-
hasSuperuserRights
|
|
64
|
-
);
|
|
71
|
+
const fields = parseSchema(collection, options.jsonSchemas);
|
|
65
72
|
|
|
66
73
|
// Check if custom id field is present
|
|
67
|
-
if (options.
|
|
74
|
+
if (options.id) {
|
|
68
75
|
// Find the id field in the schema
|
|
69
|
-
const idField = collection.
|
|
70
|
-
(field) => field.name === options.
|
|
76
|
+
const idField = collection.schema.find(
|
|
77
|
+
(field) => field.name === options.id
|
|
71
78
|
);
|
|
72
79
|
|
|
73
80
|
// Check if the id field is present and of a valid type
|
|
74
81
|
if (!idField) {
|
|
75
82
|
console.error(
|
|
76
|
-
`The id field "${options.
|
|
83
|
+
`The id field "${options.id}" is not present in the schema of the collection "${options.collectionName}".`
|
|
77
84
|
);
|
|
78
85
|
} else if (!VALID_ID_TYPES.includes(idField.type)) {
|
|
79
86
|
console.error(
|
|
80
|
-
`The id field "${options.
|
|
87
|
+
`The id field "${options.id}" for collection "${
|
|
81
88
|
options.collectionName
|
|
82
89
|
}" is of type "${
|
|
83
90
|
idField.type
|
|
@@ -89,15 +96,12 @@ export async function generateSchema(
|
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
// Check if the content field is present
|
|
92
|
-
if (
|
|
93
|
-
typeof options.contentFields === "string" &&
|
|
94
|
-
!fields[options.contentFields]
|
|
95
|
-
) {
|
|
99
|
+
if (typeof options.content === "string" && !fields[options.content]) {
|
|
96
100
|
console.error(
|
|
97
|
-
`The content field "${options.
|
|
101
|
+
`The content field "${options.content}" is not present in the schema of the collection "${options.collectionName}".`
|
|
98
102
|
);
|
|
99
|
-
} else if (Array.isArray(options.
|
|
100
|
-
for (const field of options.
|
|
103
|
+
} else if (Array.isArray(options.content)) {
|
|
104
|
+
for (const field of options.content) {
|
|
101
105
|
if (!fields[field]) {
|
|
102
106
|
console.error(
|
|
103
107
|
`The content field "${field}" is not present in the schema of the collection "${options.collectionName}".`
|
|
@@ -106,39 +110,18 @@ export async function generateSchema(
|
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
console.error(
|
|
113
|
-
`The field "${options.updatedField}" is not present in the schema of the collection "${options.collectionName}".\nThis will lead to errors when trying to fetch only updated entries.`
|
|
114
|
-
);
|
|
115
|
-
} else {
|
|
116
|
-
const updatedField = collection.fields.find(
|
|
117
|
-
(field) => field.name === options.updatedField
|
|
118
|
-
);
|
|
119
|
-
if (
|
|
120
|
-
!updatedField ||
|
|
121
|
-
updatedField.type !== "autodate" ||
|
|
122
|
-
!updatedField.onUpdate
|
|
123
|
-
) {
|
|
124
|
-
console.warn(
|
|
125
|
-
`The field "${options.updatedField}" is not of type "autodate" with the value "Update" or "Create/Update".\nMake sure that the field is automatically updated when the entry is updated!`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
113
|
+
// Use the corresponding base schema for the type of collection
|
|
114
|
+
// Auth collections are basically a superset of the basic schema.
|
|
115
|
+
const base = collection.type === "view" ? VIEW_SCHEMA : BASIC_SCHEMA;
|
|
130
116
|
|
|
131
117
|
// Combine the basic schema with the parsed fields
|
|
132
118
|
const schema = z.object({
|
|
133
|
-
...
|
|
119
|
+
...base,
|
|
134
120
|
...fields
|
|
135
121
|
});
|
|
136
122
|
|
|
137
123
|
// Get all file fields
|
|
138
|
-
const fileFields = collection.
|
|
139
|
-
.filter((field) => field.type === "file")
|
|
140
|
-
// Only show hidden fields if the user has superuser rights
|
|
141
|
-
.filter((field) => !field.hidden || hasSuperuserRights);
|
|
124
|
+
const fileFields = collection.schema.filter((field) => field.type === "file");
|
|
142
125
|
|
|
143
126
|
if (fileFields.length === 0) {
|
|
144
127
|
return schema;
|
|
@@ -146,6 +129,7 @@ export async function generateSchema(
|
|
|
146
129
|
|
|
147
130
|
// Transform file names to file urls
|
|
148
131
|
return schema.transform((entry) =>
|
|
132
|
+
// @ts-expect-error - `updated` and `created` are already transformed to dates
|
|
149
133
|
transformFiles(options.url, fileFields, entry)
|
|
150
134
|
);
|
|
151
135
|
}
|
package/src/load-entries.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { parseEntry } from "./utils/parse-entry";
|
|
|
7
7
|
*
|
|
8
8
|
* @param options Options for the loader.
|
|
9
9
|
* @param context Context of the loader.
|
|
10
|
-
* @param
|
|
10
|
+
* @param adminToken Admin token to access all resources.
|
|
11
11
|
* @param lastModified Date of the last fetch to only update changed entries.
|
|
12
12
|
*
|
|
13
13
|
* @returns `true` if the collection has an updated column, `false` otherwise.
|
|
@@ -15,34 +15,35 @@ import { parseEntry } from "./utils/parse-entry";
|
|
|
15
15
|
export async function loadEntries(
|
|
16
16
|
options: PocketBaseLoaderOptions,
|
|
17
17
|
context: LoaderContext,
|
|
18
|
-
|
|
18
|
+
adminToken: string | undefined,
|
|
19
19
|
lastModified: string | undefined
|
|
20
|
-
): Promise<
|
|
20
|
+
): Promise<boolean> {
|
|
21
21
|
// Build the URL for the collections endpoint
|
|
22
22
|
const collectionUrl = new URL(
|
|
23
23
|
`api/collections/${options.collectionName}/records`,
|
|
24
24
|
options.url
|
|
25
25
|
).href;
|
|
26
26
|
|
|
27
|
-
// Create the headers for the request to append the
|
|
27
|
+
// Create the headers for the request to append the admin token (if available)
|
|
28
28
|
const collectionHeaders = new Headers();
|
|
29
|
-
if (
|
|
30
|
-
collectionHeaders.set("Authorization",
|
|
29
|
+
if (adminToken) {
|
|
30
|
+
collectionHeaders.set("Authorization", adminToken);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Log the fetching of the entries
|
|
34
34
|
context.logger.info(
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
}`
|
|
35
|
+
`Fetching${lastModified ? " modified" : ""} data for ${
|
|
36
|
+
context.collection
|
|
37
|
+
} from ${collectionUrl}${
|
|
38
|
+
lastModified ? ` starting at ${lastModified}` : ""
|
|
39
|
+
}${adminToken ? " with admin token" : ""}`
|
|
40
40
|
);
|
|
41
41
|
|
|
42
42
|
// Prepare pagination variables
|
|
43
43
|
let page = 0;
|
|
44
44
|
let totalPages = 0;
|
|
45
45
|
let entries = 0;
|
|
46
|
+
let hasUpdatedColumn = !!lastModified;
|
|
46
47
|
|
|
47
48
|
// Fetch all (modified) entries
|
|
48
49
|
do {
|
|
@@ -50,8 +51,8 @@ export async function loadEntries(
|
|
|
50
51
|
// If `lastModified` is set, only fetch entries that have been modified since the last fetch
|
|
51
52
|
const collectionRequest = await fetch(
|
|
52
53
|
`${collectionUrl}?page=${++page}&perPage=100${
|
|
53
|
-
lastModified
|
|
54
|
-
? `&sort
|
|
54
|
+
lastModified
|
|
55
|
+
? `&sort=-updated,id&filter=(updated>"${lastModified}")`
|
|
55
56
|
: ""
|
|
56
57
|
}`,
|
|
57
58
|
{
|
|
@@ -61,10 +62,10 @@ export async function loadEntries(
|
|
|
61
62
|
|
|
62
63
|
// If the request was not successful, print the error message and return
|
|
63
64
|
if (!collectionRequest.ok) {
|
|
64
|
-
// If the collection is locked, an
|
|
65
|
+
// If the collection is locked, an admin token is required
|
|
65
66
|
if (collectionRequest.status === 403) {
|
|
66
67
|
throw new Error(
|
|
67
|
-
`
|
|
68
|
+
`The collection ${options.collectionName} is not accessible without an admin rights. Please provide an admin email and password in the config.`
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -72,7 +73,7 @@ export async function loadEntries(
|
|
|
72
73
|
const reason = await collectionRequest
|
|
73
74
|
.json()
|
|
74
75
|
.then((data) => data.message);
|
|
75
|
-
const errorMessage = `
|
|
76
|
+
const errorMessage = `Fetching data from ${options.collectionName} failed with status code ${collectionRequest.status}.\nReason: ${reason}`;
|
|
76
77
|
throw new Error(errorMessage);
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -81,7 +82,13 @@ export async function loadEntries(
|
|
|
81
82
|
|
|
82
83
|
// Parse and store the entries
|
|
83
84
|
for (const entry of response.items) {
|
|
84
|
-
await parseEntry(entry, context, options);
|
|
85
|
+
await parseEntry(entry, context, options.id, options.content);
|
|
86
|
+
|
|
87
|
+
// Check if the entry has an `updated` column
|
|
88
|
+
// This is used to enable the incremental fetching of entries
|
|
89
|
+
if (!hasUpdatedColumn && "updated" in entry) {
|
|
90
|
+
hasUpdatedColumn = true;
|
|
91
|
+
}
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
// Update the page and total pages
|
|
@@ -92,8 +99,11 @@ export async function loadEntries(
|
|
|
92
99
|
|
|
93
100
|
// Log the number of fetched entries
|
|
94
101
|
context.logger.info(
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
}
|
|
102
|
+
`Fetched ${entries}${lastModified ? " changed" : ""} entries for ${
|
|
103
|
+
context.collection
|
|
104
|
+
}`
|
|
98
105
|
);
|
|
106
|
+
|
|
107
|
+
// Return if the collection has an updated column
|
|
108
|
+
return hasUpdatedColumn;
|
|
99
109
|
}
|
package/src/pocketbase-loader.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { cleanupEntries } from "./cleanup-entries";
|
|
|
4
4
|
import { generateSchema } from "./generate-schema";
|
|
5
5
|
import { loadEntries } from "./load-entries";
|
|
6
6
|
import type { PocketBaseLoaderOptions } from "./types/pocketbase-loader-options.type";
|
|
7
|
-
import {
|
|
7
|
+
import { getAdminToken } from "./utils/get-admin-token";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Loader for collections stored in PocketBase.
|
|
@@ -15,37 +15,37 @@ export function pocketbaseLoader(options: PocketBaseLoaderOptions): Loader {
|
|
|
15
15
|
return {
|
|
16
16
|
name: "pocketbase-loader",
|
|
17
17
|
load: async (context: LoaderContext): Promise<void> => {
|
|
18
|
-
// Get the date of the last fetch to only update changed entries.
|
|
19
|
-
let lastModified = context.meta.get("last-modified");
|
|
20
|
-
|
|
21
18
|
// Check if the version has changed to force an update
|
|
22
19
|
const lastVersion = context.meta.get("version");
|
|
23
20
|
if (lastVersion !== packageJson.version) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
28
|
-
}
|
|
21
|
+
context.logger.info(
|
|
22
|
+
`PocketBase loader was updated from ${lastVersion} to ${packageJson.version}. All entries will be loaded again.`
|
|
23
|
+
);
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
lastModified = undefined;
|
|
32
|
-
context.store.clear();
|
|
25
|
+
options.forceUpdate = true;
|
|
33
26
|
}
|
|
34
27
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
|
|
28
|
+
// Get the date of the last fetch to only update changed entries.
|
|
29
|
+
// If `forceUpdate` is set to `true`, this will be `undefined` to fetch all entries again.
|
|
30
|
+
const lastModified = options.forceUpdate
|
|
31
|
+
? undefined
|
|
32
|
+
: context.meta.get("last-modified");
|
|
33
|
+
|
|
34
|
+
// Get the `has-updated-column` meta to check if the collection has an updated column
|
|
35
|
+
let hasUpdatedColumn = context.meta.get("has-updated-column") === "true";
|
|
36
|
+
|
|
37
|
+
// Clear the store if we want to fetch all entries again
|
|
38
|
+
if (options.forceUpdate) {
|
|
39
|
+
context.store.clear();
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
// Try to get
|
|
42
|
+
// Try to get an admin token to access all resources.
|
|
44
43
|
let token: string | undefined;
|
|
45
|
-
if (options.
|
|
46
|
-
token = await
|
|
44
|
+
if (options.adminEmail && options.adminPassword) {
|
|
45
|
+
token = await getAdminToken(
|
|
47
46
|
options.url,
|
|
48
|
-
options.
|
|
47
|
+
options.adminEmail,
|
|
48
|
+
options.adminPassword,
|
|
49
49
|
context.logger
|
|
50
50
|
);
|
|
51
51
|
}
|
|
@@ -56,7 +56,25 @@ export function pocketbaseLoader(options: PocketBaseLoaderOptions): Loader {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Load the (modified) entries
|
|
59
|
-
|
|
59
|
+
try {
|
|
60
|
+
hasUpdatedColumn = await loadEntries(
|
|
61
|
+
options,
|
|
62
|
+
context,
|
|
63
|
+
token,
|
|
64
|
+
// Only fetch entries that have been modified since the last fetch
|
|
65
|
+
// If the collection does not have an updated column, all entries will be fetched
|
|
66
|
+
hasUpdatedColumn ? lastModified : undefined
|
|
67
|
+
);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Set the `has-updated-column` meta to `false` if an error occurred
|
|
70
|
+
// This will force the loader to fetch all entries again in the next run
|
|
71
|
+
context.meta.set("has-updated-column", `${false}`);
|
|
72
|
+
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Set the `has-updated-column` meta to `true` if the collection has an updated column
|
|
77
|
+
context.meta.set("has-updated-column", `${hasUpdatedColumn}`);
|
|
60
78
|
|
|
61
79
|
// Set the last modified date to the current date
|
|
62
80
|
context.meta.set("last-modified", new Date().toISOString());
|
|
@@ -14,6 +14,14 @@ interface PocketBaseBaseEntry {
|
|
|
14
14
|
* Name of the collection the entry belongs to.
|
|
15
15
|
*/
|
|
16
16
|
collectionName: string;
|
|
17
|
+
/**
|
|
18
|
+
* Date the entry was created.
|
|
19
|
+
*/
|
|
20
|
+
created?: string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Date the entry was last updated.
|
|
23
|
+
*/
|
|
24
|
+
updated?: string | undefined;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
/**
|
|
@@ -20,41 +20,31 @@ export interface PocketBaseLoaderOptions {
|
|
|
20
20
|
*
|
|
21
21
|
* If the field is a string, it will be slugified to be used in the URL.
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
id?: string;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* This must be the name of a field in the
|
|
25
|
+
* Content of the collection in PocketBase.
|
|
26
|
+
* This must be the name of a field in the collection that contains the content.
|
|
27
27
|
* The content will be parsed as HTML and rendered to the page.
|
|
28
28
|
*
|
|
29
29
|
* If you want to render multiple fields as main content, you can pass an array of field names.
|
|
30
30
|
* The loader will concatenate the content of all fields in the order they are defined in the array.
|
|
31
31
|
* Each block will be contained in a `<section>` element.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
content?: string | Array<string>;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* This field is used to only fetch entries that have been modified since the last build.
|
|
35
|
+
* Email of an admin to get full access to the PocketBase instance.
|
|
36
|
+
* Together with `adminPassword` this is required to get automatic type generation and to access all resources even if they are not public.
|
|
38
37
|
*/
|
|
39
|
-
|
|
38
|
+
adminEmail?: string;
|
|
40
39
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* Password of the admin to get full access to the PocketBase instance.
|
|
41
|
+
* Together with `adminEmail` this is required to get automatic type generation and to access all resources even if they are not public.
|
|
43
42
|
*/
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Email of the superuser.
|
|
47
|
-
*/
|
|
48
|
-
email: string;
|
|
49
|
-
/**
|
|
50
|
-
* Password of the superuser.
|
|
51
|
-
*/
|
|
52
|
-
password: string;
|
|
53
|
-
};
|
|
43
|
+
adminPassword?: string;
|
|
54
44
|
/**
|
|
55
45
|
* File path to the local schema file.
|
|
56
46
|
* This file will be used to generate the schema for the collection.
|
|
57
|
-
* If `
|
|
47
|
+
* If admin credentials are provided (see `adminEmail` and `adminPassword`), this option will be ignored.
|
|
58
48
|
*/
|
|
59
49
|
localSchema?: string;
|
|
60
50
|
/**
|
|
@@ -65,4 +55,9 @@ export interface PocketBaseLoaderOptions {
|
|
|
65
55
|
* Note that this will only be used for fields of type `json`.
|
|
66
56
|
*/
|
|
67
57
|
jsonSchemas?: Record<string, z.ZodSchema>;
|
|
58
|
+
/**
|
|
59
|
+
* By default, the loader will only fetch entries that have been modified since the last fetch.
|
|
60
|
+
* If you want to fetch all entries, set this to `true`.
|
|
61
|
+
*/
|
|
62
|
+
forceUpdate?: boolean;
|
|
68
63
|
}
|
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
* Entry for a collections schema in PocketBase.
|
|
3
3
|
*/
|
|
4
4
|
export interface PocketBaseSchemaEntry {
|
|
5
|
-
/**
|
|
6
|
-
* Flag to indicate if the field is hidden.
|
|
7
|
-
* Hidden fields are not returned in the API response.
|
|
8
|
-
*/
|
|
9
|
-
hidden: boolean;
|
|
10
5
|
/**
|
|
11
6
|
* Name of the field.
|
|
12
7
|
*/
|
|
@@ -20,25 +15,20 @@ export interface PocketBaseSchemaEntry {
|
|
|
20
15
|
*/
|
|
21
16
|
required: boolean;
|
|
22
17
|
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Whether the field is updated when the entry is updated.
|
|
39
|
-
* This is only present on "autodate" fields.
|
|
40
|
-
*/
|
|
41
|
-
onUpdate?: boolean;
|
|
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
|
+
};
|
|
42
32
|
}
|
|
43
33
|
|
|
44
34
|
/**
|
|
@@ -52,9 +42,9 @@ export interface PocketBaseCollection {
|
|
|
52
42
|
/**
|
|
53
43
|
* Type of the collection.
|
|
54
44
|
*/
|
|
55
|
-
type:
|
|
45
|
+
type: 'base' | 'view' | 'auth';
|
|
56
46
|
/**
|
|
57
47
|
* Schema of the collection.
|
|
58
48
|
*/
|
|
59
|
-
|
|
49
|
+
schema: Array<PocketBaseSchemaEntry>;
|
|
60
50
|
}
|
|
@@ -1,31 +1,27 @@
|
|
|
1
1
|
import type { AstroIntegrationLogger } from "astro";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* This function will get
|
|
4
|
+
* This function will get an admin token from the given PocketBase instance.
|
|
5
5
|
*
|
|
6
6
|
* @param url URL of the PocketBase instance
|
|
7
|
-
* @param
|
|
7
|
+
* @param email Email of the admin
|
|
8
|
+
* @param password Password of the admin
|
|
8
9
|
*
|
|
9
|
-
* @returns
|
|
10
|
+
* @returns An admin token to access all resources of the PocketBase instance.
|
|
10
11
|
*/
|
|
11
|
-
export async function
|
|
12
|
+
export async function getAdminToken(
|
|
12
13
|
url: string,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
password: string;
|
|
16
|
-
},
|
|
14
|
+
email: string,
|
|
15
|
+
password: string,
|
|
17
16
|
logger?: AstroIntegrationLogger
|
|
18
17
|
): Promise<string | undefined> {
|
|
19
18
|
// Build the URL for the login endpoint
|
|
20
|
-
const loginUrl = new URL(
|
|
21
|
-
`api/collections/_superusers/auth-with-password`,
|
|
22
|
-
url
|
|
23
|
-
).href;
|
|
19
|
+
const loginUrl = new URL(`api/admins/auth-with-password`, url).href;
|
|
24
20
|
|
|
25
21
|
// Create a new FormData object to send the login data
|
|
26
22
|
const loginData = new FormData();
|
|
27
|
-
loginData.set("identity",
|
|
28
|
-
loginData.set("password",
|
|
23
|
+
loginData.set("identity", email);
|
|
24
|
+
loginData.set("password", password);
|
|
29
25
|
|
|
30
26
|
// Send the login request to get a token
|
|
31
27
|
const loginRequest = await fetch(loginUrl, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
2
2
|
import type { PocketBaseCollection } from "../types/pocketbase-schema.type";
|
|
3
|
-
import {
|
|
3
|
+
import { getAdminToken } from "./get-admin-token";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Fetches the schema for the specified collection from the PocketBase instance.
|
|
@@ -10,17 +10,18 @@ import { getSuperuserToken } from "./get-superuser-token";
|
|
|
10
10
|
export async function getRemoteSchema(
|
|
11
11
|
options: PocketBaseLoaderOptions
|
|
12
12
|
): Promise<PocketBaseCollection | undefined> {
|
|
13
|
-
if (!options.
|
|
13
|
+
if (!options.adminEmail || !options.adminPassword) {
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// Get
|
|
18
|
-
const token = await
|
|
17
|
+
// Get the admin token
|
|
18
|
+
const token = await getAdminToken(
|
|
19
19
|
options.url,
|
|
20
|
-
options.
|
|
20
|
+
options.adminEmail,
|
|
21
|
+
options.adminPassword
|
|
21
22
|
);
|
|
22
23
|
|
|
23
|
-
// If the token is invalid
|
|
24
|
+
// If the token is invalid, return the basic schema
|
|
24
25
|
if (!token) {
|
|
25
26
|
return undefined;
|
|
26
27
|
}
|
|
@@ -38,7 +39,7 @@ export async function getRemoteSchema(
|
|
|
38
39
|
headers: schemaHeaders
|
|
39
40
|
});
|
|
40
41
|
|
|
41
|
-
// If the request was not successful,
|
|
42
|
+
// If the request was not successful, return the basic schema
|
|
42
43
|
if (!schemaRequest.ok) {
|
|
43
44
|
const reason = await schemaRequest.json().then((data) => data.message);
|
|
44
45
|
const errorMessage = `Fetching schema from ${options.collectionName} failed with status code ${schemaRequest.status}.\nReason: ${reason}`;
|
package/src/utils/parse-entry.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { LoaderContext } from "astro/loaders";
|
|
2
2
|
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
3
|
-
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
4
3
|
import { slugify } from "./slugify";
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -16,7 +15,8 @@ import { slugify } from "./slugify";
|
|
|
16
15
|
export async function parseEntry(
|
|
17
16
|
entry: PocketBaseEntry,
|
|
18
17
|
{ generateDigest, parseData, store, logger }: LoaderContext,
|
|
19
|
-
|
|
18
|
+
idField?: string,
|
|
19
|
+
contentFields?: string | Array<string>
|
|
20
20
|
): Promise<void> {
|
|
21
21
|
let id = entry.id;
|
|
22
22
|
if (idField) {
|
|
@@ -46,15 +46,11 @@ export async function parseEntry(
|
|
|
46
46
|
data: entry
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
// Get the updated date of the entry
|
|
50
|
-
let updated: string | undefined;
|
|
51
|
-
if (updatedField) {
|
|
52
|
-
updated = `${entry[updatedField]}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
49
|
// Generate a digest for the entry
|
|
56
|
-
//
|
|
57
|
-
|
|
50
|
+
// Normal collections use the updated date that is always updated when the entry is updated.
|
|
51
|
+
// If the entry was never updated, the created date can be used as a fallback.
|
|
52
|
+
// View collections don't necessarily publish the updated date, so the whole entry is used for the digest.
|
|
53
|
+
const digest = generateDigest(entry.updated ?? entry.created ?? entry);
|
|
58
54
|
|
|
59
55
|
if (!contentFields) {
|
|
60
56
|
// Store the entry
|
|
@@ -6,19 +6,13 @@ import type {
|
|
|
6
6
|
|
|
7
7
|
export function parseSchema(
|
|
8
8
|
collection: PocketBaseCollection,
|
|
9
|
-
customSchemas: Record<string, z.ZodType> | undefined
|
|
10
|
-
hasSuperuserRights: boolean
|
|
9
|
+
customSchemas: Record<string, z.ZodType> | undefined
|
|
11
10
|
): Record<string, z.ZodType> {
|
|
12
11
|
// Prepare the schemas fields
|
|
13
12
|
const fields: Record<string, z.ZodType> = {};
|
|
14
13
|
|
|
15
14
|
// Parse every field in the schema
|
|
16
|
-
for (const field of collection.
|
|
17
|
-
// Skip hidden fields if the user does not have superuser rights
|
|
18
|
-
if (field.hidden && !hasSuperuserRights) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
|
|
15
|
+
for (const field of collection.schema) {
|
|
22
16
|
let fieldType;
|
|
23
17
|
|
|
24
18
|
// Determine the field type and create the corresponding Zod type
|
|
@@ -32,12 +26,11 @@ export function parseSchema(
|
|
|
32
26
|
fieldType = z.coerce.boolean();
|
|
33
27
|
break;
|
|
34
28
|
case "date":
|
|
35
|
-
case "autodate":
|
|
36
29
|
// Coerce and parse the value as a date
|
|
37
30
|
fieldType = z.coerce.date();
|
|
38
31
|
break;
|
|
39
32
|
case "select":
|
|
40
|
-
if (!field.values) {
|
|
33
|
+
if (!field.options.values) {
|
|
41
34
|
throw new Error(
|
|
42
35
|
`Field ${field.name} is of type "select" but has no values defined.`
|
|
43
36
|
);
|
|
@@ -45,7 +38,7 @@ export function parseSchema(
|
|
|
45
38
|
|
|
46
39
|
// Create an enum for the select values
|
|
47
40
|
// @ts-expect-error - Zod complains because the values are not known at compile time and thus the array is not static.
|
|
48
|
-
const values = z.enum(field.values);
|
|
41
|
+
const values = z.enum(field.options.values);
|
|
49
42
|
|
|
50
43
|
// Parse the field type based on the number of values it can have
|
|
51
44
|
fieldType = parseSingleOrMultipleValues(field, values);
|
|
@@ -73,12 +66,8 @@ export function parseSchema(
|
|
|
73
66
|
break;
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
// Check if the field is required (onCreate autodate fields are always set)
|
|
77
|
-
const isRequired =
|
|
78
|
-
field.required || (field.type === "autodate" && field.onCreate);
|
|
79
|
-
|
|
80
69
|
// If the field is not required, mark it as optional
|
|
81
|
-
if (!
|
|
70
|
+
if (!field.required) {
|
|
82
71
|
fieldType = z.preprocess(
|
|
83
72
|
(val) => val || undefined,
|
|
84
73
|
z.optional(fieldType)
|
|
@@ -105,7 +94,7 @@ function parseSingleOrMultipleValues(
|
|
|
105
94
|
type: z.ZodType
|
|
106
95
|
) {
|
|
107
96
|
// If the select allows multiple values, create an array of the enum
|
|
108
|
-
if (field.maxSelect === undefined || field.maxSelect === 1) {
|
|
97
|
+
if (field.options.maxSelect === undefined || field.options.maxSelect === 1) {
|
|
109
98
|
return type;
|
|
110
99
|
} else {
|
|
111
100
|
return z.array(type);
|
|
@@ -17,7 +17,7 @@ export function transformFiles(
|
|
|
17
17
|
for (const field of fileFields) {
|
|
18
18
|
const fieldName = field.name;
|
|
19
19
|
|
|
20
|
-
if (field.maxSelect === 1) {
|
|
20
|
+
if (field.options.maxSelect === 1) {
|
|
21
21
|
const fileName = entry[fieldName] as string | undefined;
|
|
22
22
|
// Check if a file name is present
|
|
23
23
|
if (!fileName) {
|