astro-loader-pocketbase 3.1.1 → 3.1.2-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +26 -14
  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,200 +0,0 @@
1
- import { z } from "astro/zod";
2
- import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
3
- import type { PocketBaseCollection } from "../types/pocketbase-schema.type";
4
- import { combineFieldsForRequest } from "../utils/combine-fields-for-request";
5
- import { extractFieldNames } from "../utils/extract-field-names";
6
- import { formatFields } from "../utils/format-fields";
7
- import { getRemoteSchema } from "./get-remote-schema";
8
- import { parseSchema } from "./parse-schema";
9
- import { readLocalSchema } from "./read-local-schema";
10
- import { transformFiles } from "./transform-files";
11
-
12
- /**
13
- * Basic schema for every PocketBase collection.
14
- */
15
- const BASIC_SCHEMA = z.object({
16
- id: z.string().meta({
17
- title: "id",
18
- description: "The unique identifier for the entry."
19
- }),
20
- collectionId: z.string().meta({
21
- title: "collectionId",
22
- description:
23
- "The unique identifier for the collection the entity belongs to."
24
- }),
25
- collectionName: z.string().meta({
26
- title: "collectionName",
27
- description: "The name of the collection the entity belongs to."
28
- })
29
- });
30
-
31
- /**
32
- * Types of fields that can be used as an ID.
33
- */
34
- const VALID_ID_TYPES = ["text", "number", "email", "url", "date"];
35
-
36
- /**
37
- * Generate a schema for the collection based on the collection's schema in PocketBase.
38
- * By default, a basic schema is returned if no other schema is available.
39
- * If superuser credentials are provided, the schema is fetched from the PocketBase API.
40
- * If a path to a local schema file is provided, the schema is read from the file.
41
- *
42
- * @param options Options for the loader. See {@link PocketBaseLoaderOptions} for more details.
43
- * @param token The superuser token to authenticate the request.
44
- */
45
- // oxlint-disable-next-line explicit-module-boundary-types
46
- export async function generateSchema(
47
- options: PocketBaseLoaderOptions,
48
- token: string | undefined
49
- ) {
50
- let collection: PocketBaseCollection | undefined;
51
-
52
- if (token) {
53
- // Try to get the schema directly from the PocketBase instance
54
- collection = await getRemoteSchema(options, token);
55
- }
56
-
57
- const hasSuperuserRights = !!collection || !!options.superuserCredentials;
58
-
59
- // If the schema is not available, try to read it from a local schema file
60
- if (!collection && options.localSchema) {
61
- collection = await readLocalSchema(
62
- options.localSchema,
63
- options.collectionName
64
- );
65
- }
66
-
67
- // If the schema is still not available, return the basic schema
68
- if (!collection) {
69
- console.error(
70
- `No schema available for "${options.collectionName}". Only basic types are available. Please check your configuration and provide a valid schema file or superuser credentials.`
71
- );
72
- // Return the basic schema since every collection has at least these fields
73
- return BASIC_SCHEMA;
74
- }
75
-
76
- // Get fields to include from options
77
- const formattedFields = formatFields(options.fields);
78
- const fieldNames = extractFieldNames(formattedFields);
79
- const fieldsToInclude = combineFieldsForRequest(fieldNames, options);
80
-
81
- // Parse the schema with optional field filtering
82
- const fields = parseSchema(collection, options.jsonSchemas, {
83
- hasSuperuserRights,
84
- fieldsToInclude,
85
- experimentalLiveTypesOnly: options.experimental?.liveTypesOnly
86
- });
87
-
88
- // Do some sanity checks on the provided options
89
- checkCustomIdField(collection, options);
90
- checkContentField(fields, options);
91
- checkUpdatedField(fields, collection, options);
92
-
93
- // Combine the basic schema with the parsed fields
94
- const schema = z.object({
95
- ...BASIC_SCHEMA.shape,
96
- ...fields
97
- });
98
-
99
- // Get all file fields
100
- const fileFields = collection.fields
101
- .filter((field) => field.type === "file")
102
- // Only show hidden fields if the user has superuser rights
103
- .filter((field) => !field.hidden || hasSuperuserRights);
104
-
105
- if (fileFields.length === 0) {
106
- return schema;
107
- }
108
-
109
- // Transform file names to file urls
110
- return schema.transform((entry) =>
111
- transformFiles(options.url, fileFields, entry)
112
- );
113
- }
114
-
115
- /**
116
- * Check if the custom id field is present
117
- */
118
- function checkCustomIdField(
119
- collection: PocketBaseCollection,
120
- options: PocketBaseLoaderOptions
121
- ): void {
122
- if (!options.idField) {
123
- return;
124
- }
125
-
126
- // Find the id field in the schema
127
- const idField = collection.fields.find(
128
- (field) => field.name === options.idField
129
- );
130
-
131
- // Check if the id field is present and of a valid type
132
- if (!idField) {
133
- console.error(
134
- `The id field "${options.idField}" is not present in the schema of the collection "${options.collectionName}".`
135
- );
136
- } else if (!VALID_ID_TYPES.includes(idField.type)) {
137
- console.error(
138
- `The id field "${options.idField}" for collection "${
139
- options.collectionName
140
- }" is of type "${
141
- idField.type
142
- }" which is not recommended. Please use one of the following types: ${VALID_ID_TYPES.join(
143
- ", "
144
- )}.`
145
- );
146
- }
147
- }
148
-
149
- /**
150
- * Check if the content field(s) are present
151
- */
152
- function checkContentField(
153
- fields: Record<string, z.ZodType>,
154
- options: PocketBaseLoaderOptions
155
- ): void {
156
- if (
157
- typeof options.contentFields === "string" &&
158
- !fields[options.contentFields]
159
- ) {
160
- console.error(
161
- `The content field "${options.contentFields}" is not present in the schema of the collection "${options.collectionName}".`
162
- );
163
- } else if (Array.isArray(options.contentFields)) {
164
- for (const field of options.contentFields) {
165
- if (!fields[field]) {
166
- console.error(
167
- `The content field "${field}" is not present in the schema of the collection "${options.collectionName}".`
168
- );
169
- }
170
- }
171
- }
172
- }
173
-
174
- /**
175
- * Check if the updated field is present
176
- */
177
- function checkUpdatedField(
178
- fields: Record<string, z.ZodType>,
179
- collection: PocketBaseCollection,
180
- options: PocketBaseLoaderOptions
181
- ): void {
182
- if (!options.updatedField) {
183
- return;
184
- }
185
-
186
- if (!fields[options.updatedField]) {
187
- console.error(
188
- `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.`
189
- );
190
- } else {
191
- const updatedField = collection.fields.find(
192
- (field) => field.name === options.updatedField
193
- );
194
- if (updatedField?.type !== "autodate" || !updatedField.onUpdate) {
195
- console.warn(
196
- `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!`
197
- );
198
- }
199
- }
200
- }
@@ -1,23 +0,0 @@
1
- import type { ZodObject, ZodPipe } from "astro/zod";
2
- import {
3
- createAuxiliaryTypeStore,
4
- createTypeAlias,
5
- printNode,
6
- zodToTs
7
- } from "zod-to-ts";
8
-
9
- /**
10
- * Generate a TypeScript type from a Zod schema.
11
- */
12
- export function generateType(schema: ZodObject | ZodPipe): string {
13
- // If the collection contains file fields, we transform the field values to file urls in a `transform` step.
14
- // This means that the schema for the types is different from the schema for the data, so we need to extract the inner schema for the types.
15
- const schemaForTypes = schema.type === "pipe" ? schema.in : schema;
16
-
17
- const { node } = zodToTs(schemaForTypes, {
18
- auxiliaryTypeStore: createAuxiliaryTypeStore()
19
- });
20
- const typeAlias = createTypeAlias(node, "Entry");
21
-
22
- return `export ${printNode(typeAlias)}`;
23
- }
@@ -1,43 +0,0 @@
1
- import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
2
- import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
3
- import type { PocketBaseCollection } from "../types/pocketbase-schema.type";
4
- import { pocketBaseCollection } from "../types/pocketbase-schema.type";
5
-
6
- /**
7
- * Fetches the schema for the specified collection from the PocketBase instance.
8
- *
9
- * @param options Options for the loader. See {@link PocketBaseLoaderOptions} for more details.
10
- * @param token The superuser token to authenticate the request.
11
- */
12
- export async function getRemoteSchema(
13
- options: Pick<PocketBaseLoaderOptions, "collectionName" | "url">,
14
- token: string
15
- ): Promise<PocketBaseCollection | undefined> {
16
- // Build URL and headers for the schema request
17
- const schemaUrl = new URL(
18
- `api/collections/${options.collectionName}`,
19
- options.url
20
- ).href;
21
- const schemaHeaders = new Headers();
22
- schemaHeaders.set("Authorization", token);
23
-
24
- // Fetch the schema
25
- const schemaRequest = await fetch(schemaUrl, {
26
- headers: schemaHeaders
27
- });
28
-
29
- // If the request was not successful, try another method
30
- if (!schemaRequest.ok) {
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}`;
35
- console.error(errorMessage);
36
-
37
- return undefined;
38
- }
39
-
40
- // Get the schema from the response
41
- const response = pocketBaseCollection.parse(await schemaRequest.json());
42
- return response;
43
- }
@@ -1,170 +0,0 @@
1
- import { z } from "astro/zod";
2
- import type {
3
- PocketBaseCollection,
4
- PocketBaseSchemaEntry
5
- } from "../types/pocketbase-schema.type";
6
-
7
- export interface ParseSchemaOptions {
8
- hasSuperuserRights: boolean;
9
- fieldsToInclude?: Array<string>;
10
- experimentalLiveTypesOnly?: boolean;
11
- }
12
-
13
- /**
14
- * Converts PocketBase collection fields into Zod types, handling field types, required status, and custom schemas.
15
- */
16
- export function parseSchema(
17
- collection: PocketBaseCollection,
18
- customSchemas: Record<string, z.ZodType> | undefined,
19
- options: ParseSchemaOptions
20
- ): Record<string, z.ZodType> {
21
- // Prepare the schemas fields
22
- const fields: Record<string, z.ZodType> = {};
23
-
24
- // Parse every field in the schema
25
- for (const field of collection.fields) {
26
- // If fieldsToInclude is specified, only include fields that are in the list
27
- if (
28
- options.fieldsToInclude &&
29
- !options.fieldsToInclude.includes(field.name)
30
- ) {
31
- continue;
32
- }
33
-
34
- // Skip hidden fields if the user does not have superuser rights
35
- if (field.hidden && !options.hasSuperuserRights) {
36
- if (options.fieldsToInclude) {
37
- console.warn(
38
- `"${field.name}" is requested but hidden. Provide superuser credentials to include this field.`
39
- );
40
- }
41
-
42
- continue;
43
- }
44
-
45
- let fieldType: z.ZodType;
46
-
47
- // Determine the field type and create the corresponding Zod type
48
- switch (field.type) {
49
- case "number":
50
- fieldType = z.number();
51
- break;
52
- case "bool":
53
- fieldType = z.boolean();
54
- break;
55
- case "date":
56
- case "autodate":
57
- if (options.experimentalLiveTypesOnly) {
58
- // If experimental live types only mode is enabled, treat dates as strings
59
- fieldType = z.string();
60
- break;
61
- }
62
- // Coerce and parse the value as a date
63
- fieldType = z.coerce.date();
64
- break;
65
- case "geoPoint":
66
- fieldType = z.object({
67
- lon: z.number(),
68
- lat: z.number()
69
- });
70
- break;
71
- case "select": {
72
- if (!field.values) {
73
- throw new Error(
74
- `Field ${field.name} is of type "select" but has no values defined.`
75
- );
76
- }
77
-
78
- // Create an enum for the select values
79
- // oxlint-disable-next-line no-unsafe-type-assertion
80
- const values = z.enum(field.values as [string, ...Array<string>]);
81
-
82
- // Parse the field type based on the number of values it can have
83
- fieldType = parseSingleOrMultipleValues(field, values);
84
- break;
85
- }
86
- case "relation":
87
- case "file":
88
- // NOTE: Relations are currently not supported and are treated as strings
89
- // NOTE: Files are later transformed to URLs
90
-
91
- // Parse the field type based on the number of values it can have
92
- fieldType = parseSingleOrMultipleValues(field, z.string());
93
- break;
94
- case "json":
95
- // Use the user defined custom schema for the field or fallback to unknown
96
- fieldType = customSchemas?.[field.name] ?? z.unknown();
97
- break;
98
- default:
99
- // Default to a string
100
- fieldType = z.string();
101
- break;
102
- }
103
-
104
- const isRequired =
105
- // Check if the field is required
106
- field.required ||
107
- // `onCreate autodate` fields are always set
108
- (field.type === "autodate" && field.onCreate) ||
109
- // number and bool fields are always set
110
- field.type === "number" ||
111
- field.type === "bool";
112
-
113
- // If the field is not required, mark it as optional
114
- if (!isRequired) {
115
- fieldType = z.preprocess(
116
- (val) => val || undefined,
117
- z.optional(fieldType)
118
- );
119
- }
120
-
121
- // Add the field to the fields object
122
- fields[field.name] = fieldType.meta({
123
- id: field.id,
124
- title: field.name,
125
- description: getFieldDescription(field)
126
- });
127
- }
128
-
129
- return fields;
130
- }
131
-
132
- /**
133
- * Parse the field type based on the number of values it can have
134
- *
135
- * @param field Field to parse
136
- * @param type Type of each value
137
- *
138
- * @returns The parsed field type
139
- */
140
- function parseSingleOrMultipleValues(
141
- field: PocketBaseSchemaEntry,
142
- type: z.ZodType
143
- ): z.ZodType {
144
- // If the select allows multiple values, create an array of the enum
145
- if (field.maxSelect === undefined || field.maxSelect === 1) {
146
- return type;
147
- }
148
-
149
- return z.array(type);
150
- }
151
-
152
- /**
153
- * Get the description for a field based on its help text and type.
154
- */
155
- function getFieldDescription(field: PocketBaseSchemaEntry): string | undefined {
156
- switch (true) {
157
- case !!field.help:
158
- return field.help;
159
- case field.type === "autodate" && field.onUpdate:
160
- return "Date when the entry was last updated. This field is automatically updated by PocketBase whenever the entry is updated.";
161
- case field.type === "autodate" && field.onCreate:
162
- return "Date when the entry was created. This field is automatically set by PocketBase when the entry is created.";
163
- case field.name === "id":
164
- return "The unique identifier for the entry.";
165
- case field.hidden:
166
- return "This field is hidden and may require superuser credentials to access.";
167
- default:
168
- return undefined;
169
- }
170
- }
@@ -1,46 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
- import type { PocketBaseCollection } from "../types/pocketbase-schema.type";
4
- import { pocketBaseDatabase } from "../types/pocketbase-schema.type";
5
-
6
- /**
7
- * Reads the local PocketBase schema file and returns the schema for the specified collection.
8
- *
9
- * @param localSchemaPath Path to the local schema file.
10
- * @param collectionName Name of the collection to get the schema for.
11
- */
12
- export async function readLocalSchema(
13
- localSchemaPath: string,
14
- collectionName: string
15
- ): Promise<PocketBaseCollection | undefined> {
16
- const realPath = path.join(process.cwd(), localSchemaPath);
17
-
18
- try {
19
- // Read the schema file
20
- const schemaFile = await fs.readFile(realPath, "utf-8");
21
- const fileContent = pocketBaseDatabase.safeParse(JSON.parse(schemaFile));
22
-
23
- // Check if the database file is valid
24
- if (!fileContent.success) {
25
- throw new Error("Invalid schema file");
26
- }
27
-
28
- // Find and return the schema for the collection
29
- const schema = fileContent.data.find(
30
- (collection) => collection.name === collectionName
31
- );
32
-
33
- if (!schema) {
34
- throw new Error(
35
- `Collection "${collectionName}" not found in schema file`
36
- );
37
- }
38
-
39
- return schema;
40
- } catch (error) {
41
- console.error(
42
- `Failed to read local schema from ${localSchemaPath}. No types will be generated.\nReason: ${error}`
43
- );
44
- return undefined;
45
- }
46
- }
@@ -1,67 +0,0 @@
1
- import { z } from "astro/zod";
2
- import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
3
- import type { PocketBaseSchemaEntry } from "../types/pocketbase-schema.type";
4
-
5
- /**
6
- * Transforms file names in a PocketBase entry to file URLs.
7
- *
8
- * @param baseUrl URL of the PocketBase instance.
9
- * @param collection Collection of the entry.
10
- * @param entry Entry to transform.
11
- */
12
- export function transformFiles(
13
- baseUrl: string,
14
- fileFields: Array<PocketBaseSchemaEntry>,
15
- entry: PocketBaseEntry
16
- ): PocketBaseEntry {
17
- // Transform all file names to file URLs
18
- for (const field of fileFields) {
19
- const fieldName = field.name;
20
-
21
- if (field.maxSelect === 1) {
22
- const fileName = z.optional(z.string()).parse(entry[fieldName]);
23
- // Check if a file name is present
24
- if (!fileName) {
25
- continue;
26
- }
27
-
28
- // Transform the file name to a file URL
29
- entry[fieldName] = transformFileUrl(
30
- baseUrl,
31
- entry.collectionName,
32
- entry.id,
33
- fileName
34
- );
35
- } else {
36
- const fileNames = z.optional(z.array(z.string())).parse(entry[fieldName]);
37
- // Check if file names are present
38
- if (!fileNames) {
39
- continue;
40
- }
41
-
42
- // Transform all file names to file URLs
43
- entry[fieldName] = fileNames.map((file) =>
44
- transformFileUrl(baseUrl, entry.collectionName, entry.id, file)
45
- );
46
- }
47
- }
48
-
49
- return entry;
50
- }
51
-
52
- /**
53
- * Transforms a file name to a PocketBase file URL.
54
- *
55
- * @param base Base URL of the PocketBase instance.
56
- * @param collectionName Name of the collection.
57
- * @param entryId ID of the entry.
58
- * @param file Name of the file.
59
- */
60
- export function transformFileUrl(
61
- base: string,
62
- collectionName: string,
63
- entryId: string,
64
- file: string
65
- ): string {
66
- return new URL(`api/files/${collectionName}/${entryId}/${file}`, base).href;
67
- }
package/src/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../tsconfig.json",
3
- "include": ["./**/*"],
4
- "compilerOptions": {
5
- "noUncheckedIndexedAccess": true
6
- }
7
- }
@@ -1,19 +0,0 @@
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
- }
@@ -1,40 +0,0 @@
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,24 +0,0 @@
1
- import { z } from "astro/zod";
2
-
3
- /**
4
- * Schema for a PocketBase entry.
5
- */
6
- export const pocketBaseEntry = z.looseObject({
7
- /**
8
- * ID of the entry.
9
- */
10
- id: z.string(),
11
- /**
12
- * ID of the collection the entry belongs to.
13
- */
14
- collectionId: z.string(),
15
- /**
16
- * Name of the collection the entry belongs to.
17
- */
18
- collectionName: z.string()
19
- });
20
-
21
- /**
22
- * Type for a PocketBase entry.
23
- */
24
- export type PocketBaseEntry = z.infer<typeof pocketBaseEntry>;