astro-loader-pocketbase 2.9.0 → 2.10.0-live-expand.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 +44 -0
- package/package.json +1 -1
- package/src/loader/fetch-collection.ts +19 -3
- package/src/loader/fetch-entry.ts +17 -2
- package/src/schema/generate-schema.ts +5 -1
- package/src/types/errors.ts +18 -0
- package/src/types/pocketbase-loader-options.type.ts +30 -1
- package/src/types/pocketbase-schema.type.ts +10 -1
- package/src/utils/format-expand.ts +41 -0
package/README.md
CHANGED
|
@@ -309,6 +309,50 @@ const blogLive = defineLiveCollection({
|
|
|
309
309
|
});
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
+
### Expanding relations
|
|
313
|
+
|
|
314
|
+
The live loader supports expanding relation fields directly in the API request, which will include the related records in the response.
|
|
315
|
+
This is useful when you need to access related data without making additional requests.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
const blogLive = defineLiveCollection({
|
|
319
|
+
loader: experimentalPocketbaseLiveLoader({
|
|
320
|
+
...options,
|
|
321
|
+
experimental: {
|
|
322
|
+
// Expand single relation field
|
|
323
|
+
expand: ["author"]
|
|
324
|
+
|
|
325
|
+
// Expand multiple relation fields
|
|
326
|
+
expand: ["author", "category"]
|
|
327
|
+
|
|
328
|
+
// Expand nested relations (up to 6 levels deep)
|
|
329
|
+
expand: ["author.profile", "category.parent"]
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
When you fetch entries with expanded relations, the related records will be available in the `expand` property:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
const entry = await getLiveEntry("blogLive", { id: "<entry-id>" });
|
|
339
|
+
|
|
340
|
+
// Access expanded relation data
|
|
341
|
+
console.log(entry.expand.author.name);
|
|
342
|
+
console.log(entry.expand.category.name);
|
|
343
|
+
|
|
344
|
+
// Access nested expanded relations
|
|
345
|
+
console.log(entry.expand.author.expand.profile.bio);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
> [!NOTE]
|
|
349
|
+
> The expand parameter:
|
|
350
|
+
>
|
|
351
|
+
> - Supports up to 6 levels of nested relations (enforced by PocketBase)
|
|
352
|
+
> - Must use separate array entries for each field (e.g., `["author", "category"]` not `["author,category"]`)
|
|
353
|
+
> - Is not compatible with the `fields` option (yet)
|
|
354
|
+
> - Does not support schema generation (yet) - expanded data will have `unknown | undefined` types
|
|
355
|
+
|
|
312
356
|
### Error handling
|
|
313
357
|
|
|
314
358
|
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).
|
package/package.json
CHANGED
|
@@ -9,8 +9,12 @@ import {
|
|
|
9
9
|
} from "../types/pocketbase-api-response.type";
|
|
10
10
|
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
11
11
|
import type { ExperimentalPocketBaseLiveLoaderCollectionFilter } from "../types/pocketbase-live-loader-filter.type";
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
ExperimentalPocketBaseLiveLoaderOptions,
|
|
14
|
+
PocketBaseLoaderOptions
|
|
15
|
+
} from "../types/pocketbase-loader-options.type";
|
|
13
16
|
import { combineFieldsForRequest } from "../utils/combine-fields-for-request";
|
|
17
|
+
import { formatExpand } from "../utils/format-expand";
|
|
14
18
|
import { formatFields } from "../utils/format-fields";
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -28,7 +32,7 @@ export type CollectionFilter = {
|
|
|
28
32
|
* Fetches entries from a PocketBase collection, optionally filtering by modification date and supporting pagination.
|
|
29
33
|
*/
|
|
30
34
|
export async function fetchCollection<TEntry extends PocketBaseEntry>(
|
|
31
|
-
options:
|
|
35
|
+
options: PocketBaseLoaderOptions | ExperimentalPocketBaseLiveLoaderOptions,
|
|
32
36
|
chunkLoaded: (entries: Array<TEntry>) => Promise<void>,
|
|
33
37
|
token: string | undefined,
|
|
34
38
|
collectionFilter: CollectionFilter | undefined
|
|
@@ -124,7 +128,9 @@ export async function fetchCollection<TEntry extends PocketBaseEntry>(
|
|
|
124
128
|
* Build search parameters for the PocketBase collection request.
|
|
125
129
|
*/
|
|
126
130
|
function buildSearchParams(
|
|
127
|
-
loaderOptions:
|
|
131
|
+
loaderOptions:
|
|
132
|
+
| PocketBaseLoaderOptions
|
|
133
|
+
| ExperimentalPocketBaseLiveLoaderOptions,
|
|
128
134
|
combinedFields: Array<string> | undefined,
|
|
129
135
|
collectionFilter: CollectionFilter
|
|
130
136
|
): URLSearchParams {
|
|
@@ -173,5 +179,15 @@ function buildSearchParams(
|
|
|
173
179
|
searchParams.set("fields", combinedFields.join(","));
|
|
174
180
|
}
|
|
175
181
|
|
|
182
|
+
if (loaderOptions.experimental && "expand" in loaderOptions.experimental) {
|
|
183
|
+
const expandString = formatExpand(
|
|
184
|
+
loaderOptions.experimental.expand,
|
|
185
|
+
loaderOptions.collectionName
|
|
186
|
+
);
|
|
187
|
+
if (expandString) {
|
|
188
|
+
searchParams.set("expand", expandString);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
176
192
|
return searchParams;
|
|
177
193
|
}
|
|
@@ -6,8 +6,12 @@ import { PocketBaseAuthenticationError } from "../types/errors";
|
|
|
6
6
|
import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
|
|
7
7
|
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
8
8
|
import { pocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ExperimentalPocketBaseLiveLoaderOptions,
|
|
11
|
+
PocketBaseLoaderOptions
|
|
12
|
+
} from "../types/pocketbase-loader-options.type";
|
|
10
13
|
import { combineFieldsForRequest } from "../utils/combine-fields-for-request";
|
|
14
|
+
import { formatExpand } from "../utils/format-expand";
|
|
11
15
|
import { formatFields } from "../utils/format-fields";
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -15,7 +19,7 @@ import { formatFields } from "../utils/format-fields";
|
|
|
15
19
|
*/
|
|
16
20
|
export async function fetchEntry<TEntry extends PocketBaseEntry>(
|
|
17
21
|
id: string,
|
|
18
|
-
options: ExperimentalPocketBaseLiveLoaderOptions,
|
|
22
|
+
options: PocketBaseLoaderOptions | ExperimentalPocketBaseLiveLoaderOptions,
|
|
19
23
|
token: string | undefined
|
|
20
24
|
): Promise<TEntry> {
|
|
21
25
|
// Build the URL for the entry endpoint
|
|
@@ -31,6 +35,17 @@ export async function fetchEntry<TEntry extends PocketBaseEntry>(
|
|
|
31
35
|
entryUrl.searchParams.set("fields", combinedFields.join(","));
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
// Add expand parameter if specified in experimental options
|
|
39
|
+
if (options.experimental && "expand" in options.experimental) {
|
|
40
|
+
const expandString = formatExpand(
|
|
41
|
+
options.experimental.expand,
|
|
42
|
+
options.collectionName
|
|
43
|
+
);
|
|
44
|
+
if (expandString) {
|
|
45
|
+
entryUrl.searchParams.set("expand", expandString);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
// Create the headers for the request to append the token (if available)
|
|
35
50
|
const entryHeaders = new Headers();
|
|
36
51
|
if (token) {
|
|
@@ -84,7 +84,11 @@ export async function generateSchema(
|
|
|
84
84
|
// Combine the basic schema with the parsed fields
|
|
85
85
|
const schema = z.object({
|
|
86
86
|
...BASIC_SCHEMA,
|
|
87
|
-
...fields
|
|
87
|
+
...fields,
|
|
88
|
+
// Add expand field for live types only mode to support expanded relations
|
|
89
|
+
...(options.experimental?.liveTypesOnly && {
|
|
90
|
+
expand: z.optional(z.unknown())
|
|
91
|
+
})
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
// Get all file fields
|
package/src/types/errors.ts
CHANGED
|
@@ -17,3 +17,21 @@ export class PocketBaseAuthenticationError extends LiveCollectionError {
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when there is a configuration issue with the loader.
|
|
23
|
+
*/
|
|
24
|
+
export class PocketBaseConfigurationError extends LiveCollectionError {
|
|
25
|
+
constructor(collection: string, message: string) {
|
|
26
|
+
super(collection, message);
|
|
27
|
+
this.name = "PocketBaseConfigurationError";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static is(error: unknown): error is PocketBaseConfigurationError {
|
|
31
|
+
// This is similar to the original implementation in Astro itself.
|
|
32
|
+
return (
|
|
33
|
+
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
34
|
+
!!error && (error as Error)?.name === "PocketBaseConfigurationError"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -156,4 +156,33 @@ export type PocketBaseLoaderOptions = PocketBaseLoaderBaseOptions & {
|
|
|
156
156
|
* @experimental Live content collections are still experimental
|
|
157
157
|
*/
|
|
158
158
|
export type ExperimentalPocketBaseLiveLoaderOptions =
|
|
159
|
-
PocketBaseLoaderBaseOptions
|
|
159
|
+
PocketBaseLoaderBaseOptions & {
|
|
160
|
+
/**
|
|
161
|
+
* Experimental options for the live loader.
|
|
162
|
+
*
|
|
163
|
+
* @experimental All of these options are experimental and may change in the future.
|
|
164
|
+
*/
|
|
165
|
+
experimental?: {
|
|
166
|
+
/**
|
|
167
|
+
* Specify relations to auto expand in the API response.
|
|
168
|
+
* This can be an array of relation field names to expand.
|
|
169
|
+
* Supports dot notation for nested relations up to 6 levels deep.
|
|
170
|
+
*
|
|
171
|
+
* Note: This option is not compatible with the `fields` option.
|
|
172
|
+
*
|
|
173
|
+
* Example:
|
|
174
|
+
* ```ts
|
|
175
|
+
* // Using array format:
|
|
176
|
+
* expand: ['author', 'category']
|
|
177
|
+
*
|
|
178
|
+
* // Nested relations:
|
|
179
|
+
* expand: ['author.profile', 'category.parent']
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @see {@link https://pocketbase.io/docs/working-with-relations/#expanding-relations PocketBase documentation} for valid syntax
|
|
183
|
+
*
|
|
184
|
+
* @experimental This feature is experimental and may change in the future
|
|
185
|
+
*/
|
|
186
|
+
expand?: Array<string>;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
@@ -55,7 +55,12 @@ export const pocketBaseSchemaEntry = z.object({
|
|
|
55
55
|
* Whether the field is updated when the entry is updated.
|
|
56
56
|
* This is only present on "autodate" fields.
|
|
57
57
|
*/
|
|
58
|
-
onUpdate: z.optional(z.boolean())
|
|
58
|
+
onUpdate: z.optional(z.boolean()),
|
|
59
|
+
/**
|
|
60
|
+
* Id of the associated collection that the relation is referencing.
|
|
61
|
+
* This is only present on "relation" fields.
|
|
62
|
+
*/
|
|
63
|
+
collectionId: z.optional(z.string())
|
|
59
64
|
});
|
|
60
65
|
|
|
61
66
|
/**
|
|
@@ -67,6 +72,10 @@ export type PocketBaseSchemaEntry = z.infer<typeof pocketBaseSchemaEntry>;
|
|
|
67
72
|
* Schema for a PocketBase collection.
|
|
68
73
|
*/
|
|
69
74
|
export const pocketBaseCollection = z.object({
|
|
75
|
+
/**
|
|
76
|
+
* Id of the collection.
|
|
77
|
+
*/
|
|
78
|
+
id: z.string(),
|
|
70
79
|
/**
|
|
71
80
|
* Name of the collection.
|
|
72
81
|
*/
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { PocketBaseConfigurationError } from "../types/errors";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maximum nesting depth for expand relations as enforced by PocketBase
|
|
5
|
+
*/
|
|
6
|
+
const MAX_EXPAND_DEPTH = 6;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format and validate expand option for PocketBase API requests.
|
|
10
|
+
* Validates nesting depth and returns formatted expand string.
|
|
11
|
+
*/
|
|
12
|
+
export function formatExpand(
|
|
13
|
+
expand: Array<string> | undefined,
|
|
14
|
+
collectionName: string
|
|
15
|
+
): string | undefined {
|
|
16
|
+
if (!expand || expand.length === 0) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Validate each expand field for maximum nesting depth and invalid characters
|
|
21
|
+
for (const field of expand) {
|
|
22
|
+
// Check for comma in field name
|
|
23
|
+
if (field.includes(",")) {
|
|
24
|
+
throw new PocketBaseConfigurationError(
|
|
25
|
+
collectionName,
|
|
26
|
+
`Expand field "${field}" contains a comma. Use separate array entries instead of comma-separated values.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const depth = (field.match(/\./g) || []).length + 1;
|
|
31
|
+
if (depth > MAX_EXPAND_DEPTH) {
|
|
32
|
+
throw new PocketBaseConfigurationError(
|
|
33
|
+
collectionName,
|
|
34
|
+
`Expand field "${field}" exceeds maximum nesting depth of ${MAX_EXPAND_DEPTH} levels.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Join all expand fields with comma as required by PocketBase
|
|
40
|
+
return expand.join(",");
|
|
41
|
+
}
|