astro-loader-pocketbase 3.1.0 → 3.1.2-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.
Files changed (38) hide show
  1. package/dist/index.d.mts +276 -0
  2. package/dist/index.d.mts.map +1 -0
  3. package/dist/index.mjs +973 -0
  4. package/dist/index.mjs.map +1 -0
  5. package/package.json +24 -15
  6. package/src/index.ts +0 -24
  7. package/src/loader/cleanup-entries.ts +0 -137
  8. package/src/loader/fetch-collection.ts +0 -177
  9. package/src/loader/fetch-entry.ts +0 -83
  10. package/src/loader/handle-realtime-updates.ts +0 -56
  11. package/src/loader/live-collection-loader.ts +0 -51
  12. package/src/loader/live-entry-loader.ts +0 -38
  13. package/src/loader/load-entries.ts +0 -52
  14. package/src/loader/loader.ts +0 -77
  15. package/src/loader/parse-entry.ts +0 -90
  16. package/src/loader/parse-live-entry.ts +0 -66
  17. package/src/pocketbase-loader.ts +0 -98
  18. package/src/schema/generate-schema.ts +0 -200
  19. package/src/schema/generate-type.ts +0 -23
  20. package/src/schema/get-remote-schema.ts +0 -43
  21. package/src/schema/parse-schema.ts +0 -170
  22. package/src/schema/read-local-schema.ts +0 -46
  23. package/src/schema/transform-files.ts +0 -67
  24. package/src/tsconfig.json +0 -7
  25. package/src/types/errors.ts +0 -19
  26. package/src/types/pocketbase-api-response.type.ts +0 -40
  27. package/src/types/pocketbase-entry.type.ts +0 -24
  28. package/src/types/pocketbase-live-loader-filter.type.ts +0 -55
  29. package/src/types/pocketbase-loader-options.type.ts +0 -146
  30. package/src/types/pocketbase-schema.type.ts +0 -101
  31. package/src/utils/combine-fields-for-request.ts +0 -55
  32. package/src/utils/create-token-promise.ts +0 -25
  33. package/src/utils/extract-field-names.ts +0 -15
  34. package/src/utils/format-fields.ts +0 -66
  35. package/src/utils/get-superuser-token.ts +0 -76
  36. package/src/utils/is-realtime-data.ts +0 -34
  37. package/src/utils/should-refresh.ts +0 -37
  38. package/src/utils/slugify.ts +0 -21
@@ -1,55 +0,0 @@
1
- /**
2
- * Filter for a single entry
3
- */
4
- export interface PocketBaseLiveLoaderEntryFilter {
5
- /**
6
- * Id of the entry.
7
- */
8
- id: string;
9
- }
10
-
11
- /**
12
- * Filter for a collection of entries.
13
- */
14
- export interface PocketBaseLiveLoaderCollectionFilter {
15
- /**
16
- * Additional filter to apply to the collection.
17
- * This will be added to the filter supplied in the {@link PocketBaseLiveLoaderOptions}.
18
- *
19
- * Example:
20
- * ```ts
21
- * // config:
22
- * filter: 'release >= @now && deleted = false'
23
- *
24
- * // request
25
- * `?filter=(${loaderFilter})&&(release >= @now && deleted = false)`
26
- * ```
27
- *
28
- * @see {@link https://pocketbase.io/docs/api-records/#listsearch-records PocketBase documentation} for valid syntax
29
- */
30
- filter?: string;
31
- /**
32
- * Page number to load (1-indexed).
33
- */
34
- page?: number;
35
- /**
36
- * Number of entries to load per page.
37
- * If not provided all entries will be loaded in chunks of 100.
38
- */
39
- perPage?: number;
40
- /**
41
- * Sort order in which the entries should be returned.
42
- *
43
- * Example:
44
- * ```ts
45
- * // config:
46
- * sort: '-created,id'
47
- *
48
- * // request
49
- * `?sort=-created,id`
50
- * ```
51
- *
52
- * @see {@link https://pocketbase.io/docs/api-records/#listsearch-records PocketBase documentation} for valid syntax
53
- */
54
- sort?: string;
55
- }
@@ -1,146 +0,0 @@
1
- import type { ZodType } from "astro/zod";
2
-
3
- /**
4
- * Options for both build time and live collection loader
5
- */
6
- export interface PocketBaseLoaderBaseOptions {
7
- /**
8
- * URL of the PocketBase instance.
9
- */
10
- url: string;
11
- /**
12
- * Name of the collection in PocketBase.
13
- */
14
- collectionName: string;
15
- /**
16
- * Name of the field(s) containing the content of an entry.
17
- * This must be the name of a field in the PocketBase collection that contains the content.
18
- * The content will be parsed as HTML and rendered to the page.
19
- *
20
- * If you want to render multiple fields as main content, you can pass an array of field names.
21
- * The loader will concatenate the content of all fields in the order they are defined in the array.
22
- * Each block will be contained in a `<section>` element.
23
- */
24
- contentFields?: string | Array<string>;
25
- /**
26
- * Name of the field containing the last update date of an entry.
27
- * Ideally, this field should be of type `autodate` and have the value "Update" or "Create/Update".
28
- * For the build time loader, this field is used to only fetch entries that have been modified since the last build.
29
- * For the live collection loader, this field is used to set the `lastModified` cache hint.
30
- */
31
- updatedField?: string;
32
- /**
33
- * Custom filter that is applied when loading data from PocketBase.
34
- *
35
- * The loader will also add it's own filters for incremental builds.
36
- * These will be added to your custom filter query.
37
- *
38
- * Example:
39
- * ```ts
40
- * // config:
41
- * filter: 'release >= @now && deleted = false'
42
- *
43
- * // request
44
- * `?filter=(${loaderFilter})&&(release >= @now && deleted = false)`
45
- * ```
46
- *
47
- * @see {@link https://pocketbase.io/docs/api-records/#listsearch-records PocketBase documentation} for valid syntax
48
- */
49
- filter?: string;
50
- /**
51
- * Specify which fields to return for each record.
52
- * This can be either a comma-separated string of field names or an array of field names.
53
- * Only the specified fields will be included in the response and schema.
54
- *
55
- * Use "*" to include all fields (same as not specifying the fields option).
56
- *
57
- * Note: The basic fields (`id`, `collectionId`, `collectionName`) are automatically included
58
- * in API requests when using field filtering. Additionally, any custom fields specified in the
59
- * loader options (`idField`, `updatedField`, `contentFields`) are also automatically included.
60
- *
61
- * Warning: Expand fields are not currently supported by this loader.
62
- *
63
- * Example:
64
- * ```ts
65
- * // Using string format:
66
- * fields: 'title,content,author'
67
- *
68
- * // Using array format:
69
- * fields: ['title', 'content', 'author']
70
- *
71
- * // Include all fields:
72
- * fields: '*'
73
- * ```
74
- *
75
- * @see {@link https://pocketbase.io/docs/api-records/#listsearch-records PocketBase documentation} for valid syntax
76
- */
77
- fields?: string | Array<string>;
78
- /**
79
- * Credentials of a superuser to get full access to the PocketBase instance.
80
- * This is required to get automatic type generation without a local schema, to access all resources even if they are not public and to fetch content of hidden fields.
81
- */
82
- superuserCredentials?:
83
- | {
84
- /**
85
- * Email of the superuser.
86
- */
87
- email: string;
88
- /**
89
- * Password of the superuser.
90
- */
91
- password: string;
92
- }
93
- | {
94
- /**
95
- * Impersonate auth token of the superuser.
96
- * This token will take precedence over the email and password.
97
- */
98
- impersonateToken: string;
99
- };
100
- }
101
-
102
- /**
103
- * Options for the PocketBase loader.
104
- */
105
- export type PocketBaseLoaderOptions = PocketBaseLoaderBaseOptions & {
106
- /**
107
- * Field that should be used as the unique identifier for the collection.
108
- * This must be the name of a field in the collection that contains unique values.
109
- * If not provided, the `id` field will be used.
110
- * The value of this field will be used in `getEntry` and `getEntries` to load the entry or entries.
111
- *
112
- * If the field is a string, it will be slugified to be used in the URL.
113
- */
114
- idField?: string;
115
- /**
116
- * File path to the local schema file.
117
- * This file will be used to generate the schema for the collection.
118
- * If `superuserCredentials` are provided, this option will be ignored.
119
- */
120
- localSchema?: string;
121
- /**
122
- * Record of zod schemas for all JSON fields in the collection.
123
- * The key must be the name of a field in the collection.
124
- * If no schema is provided for a field, the value will be treated as `unknown`.
125
- *
126
- * Note that this will only be used for fields of type `json`.
127
- */
128
- jsonSchemas?: Record<string, ZodType>;
129
- /**
130
- * Experimental options for the loader.
131
- *
132
- * @experimental All of these options are experimental and may change in the future.
133
- */
134
- experimental?: {
135
- /**
136
- * Whether to only create types for the live loader.
137
- * This will not load any data, but only generate types that can be used with the live loader.
138
- */
139
- liveTypesOnly?: boolean;
140
- };
141
- };
142
-
143
- /**
144
- * Options for the PocketBase live loader.
145
- */
146
- export type PocketBaseLiveLoaderOptions = PocketBaseLoaderBaseOptions;
@@ -1,101 +0,0 @@
1
- import { z } from "astro/zod";
2
-
3
- /**
4
- * Entry for a collections schema in PocketBase.
5
- */
6
- export const pocketBaseSchemaEntry = z.object({
7
- /**
8
- * Flag to indicate if the field is hidden.
9
- * Hidden fields are not returned in the API response.
10
- */
11
- hidden: z.optional(z.boolean()),
12
- /**
13
- * Unique identifier for the field.
14
- */
15
- id: z.string(),
16
- /**
17
- * Name of the field.
18
- */
19
- name: z.string(),
20
- /**
21
- * Help text for the field.
22
- * This is only present if the field has help text defined.
23
- */
24
- help: z.optional(z.string()),
25
- /**
26
- * Type of the field.
27
- */
28
- type: z.enum([
29
- "text",
30
- "editor",
31
- "number",
32
- "bool",
33
- "email",
34
- "url",
35
- "date",
36
- "autodate",
37
- "select",
38
- "file",
39
- "relation",
40
- "json",
41
- "geoPoint",
42
- "password"
43
- ]),
44
- /**
45
- * Whether the field is required.
46
- */
47
- required: z.optional(z.boolean()),
48
- /**
49
- * Values for a select field.
50
- * This is only present if the field type is "select".
51
- */
52
- values: z.optional(z.array(z.string())),
53
- /**
54
- * Maximum number of values for a select field.
55
- * This is only present on "select", "relation", and "file" fields.
56
- */
57
- maxSelect: z.optional(z.number()),
58
- /**
59
- * Whether the field is filled when the entry is created.
60
- * This is only present on "autodate" fields.
61
- */
62
- onCreate: z.optional(z.boolean()),
63
- /**
64
- * Whether the field is updated when the entry is updated.
65
- * This is only present on "autodate" fields.
66
- */
67
- onUpdate: z.optional(z.boolean())
68
- });
69
-
70
- /**
71
- * Entry for a collections schema in PocketBase.
72
- */
73
- export type PocketBaseSchemaEntry = z.infer<typeof pocketBaseSchemaEntry>;
74
-
75
- /**
76
- * Schema for a PocketBase collection.
77
- */
78
- export const pocketBaseCollection = z.object({
79
- /**
80
- * Name of the collection.
81
- */
82
- name: z.string(),
83
- /**
84
- * Type of the collection.
85
- */
86
- type: z.enum(["base", "view", "auth"]),
87
- /**
88
- * Schema of the collection.
89
- */
90
- fields: z.array(pocketBaseSchemaEntry)
91
- });
92
-
93
- /**
94
- * Type for a PocketBase collection.
95
- */
96
- export type PocketBaseCollection = z.infer<typeof pocketBaseCollection>;
97
-
98
- /**
99
- * Schema for an entire PocketBase database.
100
- */
101
- export const pocketBaseDatabase = z.array(pocketBaseCollection);
@@ -1,55 +0,0 @@
1
- import type {
2
- PocketBaseLoaderBaseOptions,
3
- PocketBaseLoaderOptions
4
- } from "../types/pocketbase-loader-options.type";
5
-
6
- /**
7
- * Combine basic, special, and user-specified fields for PocketBase API requests.
8
- * This utility ensures that required system fields are always included in API requests.
9
- *
10
- * @param userFields Array of fields specified by the user, or undefined for all fields
11
- * @param options PocketBase loader options containing custom field configurations
12
- * @returns Combined array of fields to include in the API request, or undefined for all fields
13
- */
14
- export function combineFieldsForRequest(
15
- userFields: Array<string> | undefined,
16
- options: Pick<PocketBaseLoaderBaseOptions, "updatedField" | "contentFields"> &
17
- Pick<PocketBaseLoaderOptions, "idField">
18
- ): Array<string> | undefined {
19
- // If no fields specified, return undefined to get all fields
20
- if (!userFields) {
21
- return undefined;
22
- }
23
-
24
- // Basic fields that are always required by the loader
25
- const basicFields = ["id", "collectionId", "collectionName"];
26
-
27
- // Special fields that are configured in options
28
- const specialFields: Array<string> = [];
29
-
30
- // Add custom id field if specified
31
- if (options.idField && options.idField !== "id") {
32
- specialFields.push(options.idField);
33
- }
34
-
35
- // Add updated field if specified
36
- if (options.updatedField) {
37
- specialFields.push(options.updatedField);
38
- }
39
-
40
- // Add content fields if specified
41
- if (options.contentFields) {
42
- if (Array.isArray(options.contentFields)) {
43
- specialFields.push(...options.contentFields);
44
- } else {
45
- specialFields.push(options.contentFields);
46
- }
47
- }
48
-
49
- // Combine all field sets, removing duplicates
50
- const allFields = [
51
- ...new Set([...basicFields, ...specialFields, ...userFields])
52
- ];
53
-
54
- return allFields;
55
- }
@@ -1,25 +0,0 @@
1
- import type { PocketBaseLoaderBaseOptions } from "../types/pocketbase-loader-options.type";
2
- import { getSuperuserToken } from "./get-superuser-token";
3
-
4
- /**
5
- * Creates a promise that resolves to a superuser token or undefined.
6
- */
7
- export async function createTokenPromise(
8
- options: Pick<PocketBaseLoaderBaseOptions, "superuserCredentials" | "url">
9
- ): Promise<string | undefined> {
10
- if (options.superuserCredentials) {
11
- if ("impersonateToken" in options.superuserCredentials) {
12
- // Impersonate token provided, so use it directly.
13
- return options.superuserCredentials.impersonateToken;
14
- }
15
- // Email and password provided, so get a temporary superuser token.
16
- const token = await getSuperuserToken(
17
- options.url,
18
- options.superuserCredentials
19
- );
20
- return token;
21
- }
22
-
23
- // No credentials provided, so no token can be used.
24
- return undefined;
25
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Extract field names from fields that may contain modifiers like :excerpt().
3
- *
4
- * @param fields Array of field specifications that may contain modifiers
5
- * @returns Array of clean field names suitable for schema parsing
6
- */
7
- export function extractFieldNames(
8
- fields: Array<string> | undefined
9
- ): Array<string> | undefined {
10
- if (!fields) {
11
- return undefined;
12
- }
13
-
14
- return fields.map((field) => field.split(":").at(0) ?? field);
15
- }
@@ -1,66 +0,0 @@
1
- import type { PocketBaseLoaderBaseOptions } from "../types/pocketbase-loader-options.type";
2
-
3
- /**
4
- * Format fields option into an array and validate for expand usage.
5
- * Handles wildcard "*" and preserves excerpt field modifiers.
6
- *
7
- * @param fields The fields option (string or array)
8
- * @returns Formatted fields array, or undefined if no fields specified or "*" wildcard is used
9
- */
10
- export function formatFields(
11
- fields: PocketBaseLoaderBaseOptions["fields"]
12
- ): Array<string> | undefined {
13
- if (!fields || fields.length === 0) {
14
- return undefined;
15
- }
16
-
17
- let fieldList: Array<string>;
18
- if (Array.isArray(fields)) {
19
- fieldList = fields.map((f) => f.trim());
20
- } else {
21
- // Split carefully, respecting parentheses in excerpt syntax
22
- fieldList = splitFieldsString(fields).map((f) => f.trim());
23
- }
24
-
25
- // Warn if expand is used since it's not currently supported
26
- const hasExpand = fieldList.some((field) => field.includes("expand"));
27
- if (hasExpand) {
28
- console.warn(
29
- 'The "expand" parameter is not currently supported by astro-loader-pocketbase and will be filtered out.'
30
- );
31
- fieldList = fieldList.filter((field) => !field.includes("expand"));
32
- }
33
-
34
- // Check for "*" wildcard - if found anywhere, include all fields
35
- const hasWildcard = fieldList.some((field) => field === "*");
36
- if (hasWildcard) {
37
- return undefined;
38
- }
39
-
40
- return fieldList;
41
- }
42
-
43
- /**
44
- * Splits the fields string at `,` but respects the `:excerpt(number, boolean)` option
45
- */
46
- function splitFieldsString(fieldsString: string): Array<string> {
47
- // First, split by comma
48
- const initialSplit = fieldsString.split(",");
49
-
50
- const fields: Array<string> = [];
51
- for (let i = 0; i < initialSplit.length; i++) {
52
- const part = initialSplit.at(i);
53
- if (!part) {
54
- continue;
55
- }
56
-
57
- if (part.includes("(") && !part.includes(")")) {
58
- fields.push(`${part},${initialSplit[++i]}`);
59
- continue;
60
- }
61
-
62
- fields.push(part);
63
- }
64
-
65
- return fields;
66
- }
@@ -1,76 +0,0 @@
1
- import type { AstroIntegrationLogger } from "astro";
2
- import {
3
- pocketBaseErrorResponse,
4
- pocketBaseLoginResponse
5
- } from "../types/pocketbase-api-response.type";
6
-
7
- /**
8
- * This function will get a superuser token from the given PocketBase instance.
9
- *
10
- * @param url URL of the PocketBase instance
11
- * @param superuserCredentials Credentials of the superuser
12
- *
13
- * @returns A superuser token to access all resources of the PocketBase instance.
14
- */
15
- export async function getSuperuserToken(
16
- url: string,
17
- superuserCredentials: {
18
- email: string;
19
- password: string;
20
- },
21
- logger?: AstroIntegrationLogger
22
- ): Promise<string | undefined> {
23
- // Build the URL for the login endpoint
24
- const loginUrl = new URL(
25
- `api/collections/_superusers/auth-with-password`,
26
- url
27
- ).href;
28
-
29
- // Create a new FormData object to send the login data
30
- const loginData = new FormData();
31
- loginData.set("identity", superuserCredentials.email);
32
- loginData.set("password", superuserCredentials.password);
33
-
34
- // Send the login request to get a token
35
- const loginRequest = await fetch(loginUrl, {
36
- method: "POST",
37
- body: loginData
38
- });
39
-
40
- // If the login request was not successful, print the error message and return undefined
41
- if (!loginRequest.ok) {
42
- if (loginRequest.status === 429) {
43
- const info =
44
- "A rate limit was hit while trying to authenticate with PocketBase. Consider using an `impersonateToken` as credentials to avoid this issue.";
45
- if (logger) {
46
- logger.info(info);
47
- } else {
48
- console.info(info);
49
- }
50
-
51
- // Random wait between 3 (default rate limit interval) and 8 seconds
52
- const retryAfter = Math.random() * 5 + 3;
53
- // oxlint-disable-next-line promise/avoid-new
54
- await new Promise((resolve) => {
55
- setTimeout(resolve, retryAfter * 1000);
56
- });
57
-
58
- return getSuperuserToken(url, superuserCredentials, logger);
59
- }
60
-
61
- const errorResponse = pocketBaseErrorResponse.parse(
62
- await loginRequest.json()
63
- );
64
- 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}`;
65
- if (logger) {
66
- logger.error(errorMessage);
67
- } else {
68
- console.error(errorMessage);
69
- }
70
- return undefined;
71
- }
72
-
73
- // Return the token
74
- const response = pocketBaseLoginResponse.parse(await loginRequest.json());
75
- return response.token;
76
- }
@@ -1,34 +0,0 @@
1
- import { z } from "astro/zod";
2
-
3
- /**
4
- * Schema for realtime data received from PocketBase.
5
- */
6
- const realtimeDataSchema = z.object({
7
- action: z.union([
8
- z.literal("create"),
9
- z.literal("update"),
10
- z.literal("delete")
11
- ]),
12
- record: z.object({
13
- id: z.string(),
14
- collectionName: z.string(),
15
- collectionId: z.string()
16
- })
17
- });
18
-
19
- /**
20
- * Type for realtime data received from PocketBase.
21
- */
22
- export type RealtimeData = z.infer<typeof realtimeDataSchema>;
23
-
24
- /**
25
- * Checks if the given data is realtime data received from PocketBase.
26
- */
27
- export function isRealtimeData(data: unknown): data is RealtimeData {
28
- try {
29
- realtimeDataSchema.parse(data);
30
- return true;
31
- } catch {
32
- return false;
33
- }
34
- }
@@ -1,37 +0,0 @@
1
- import type { LoaderContext } from "astro/loaders";
2
-
3
- /**
4
- * Checks if the collection should be refreshed.
5
- */
6
- export function shouldRefresh(
7
- context: LoaderContext["refreshContextData"],
8
- collectionName: string
9
- ): "refresh" | "skip" | "force" {
10
- // Check if the refresh was triggered by the `astro-integration-pocketbase`
11
- // and the correct metadata is provided.
12
- if (context?.source !== "astro-integration-pocketbase") {
13
- return "refresh";
14
- }
15
-
16
- // Check if all collections should be refreshed.
17
- if (context.force) {
18
- return "force";
19
- }
20
-
21
- if (!context.collection) {
22
- return "refresh";
23
- }
24
-
25
- // Check if the collection name matches the current collection.
26
- if (typeof context.collection === "string") {
27
- return context.collection === collectionName ? "refresh" : "skip";
28
- }
29
-
30
- // Check if the collection is included in the list of collections.
31
- if (Array.isArray(context.collection)) {
32
- return context.collection.includes(collectionName) ? "refresh" : "skip";
33
- }
34
-
35
- // Should not happen but return true to be safe.
36
- return "refresh";
37
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * Convert a string to a slug.
3
- *
4
- * Example:
5
- * ```ts
6
- * slugify("Hello World!"); // hello-world
7
- * ```
8
- */
9
- export function slugify(input: string): string {
10
- return input
11
- .toLowerCase()
12
- .replaceAll(/\s+/g, "-") // Replace spaces with -
13
- .replaceAll("ä", "ae") // Replace umlauts
14
- .replaceAll("ö", "oe")
15
- .replaceAll("ü", "ue")
16
- .replaceAll("ß", "ss")
17
- .replaceAll(/[^\w-]+/g, "") // Remove all non-word chars
18
- .replaceAll(/--+/g, "-") // Replace multiple - with single -
19
- .replace(/^-+/, "") // Trim - from start of text
20
- .replace(/-+$/, ""); // Trim - from end of text
21
- }