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.
- package/dist/index.d.mts +276 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +973 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +26 -14
- package/src/index.ts +0 -24
- package/src/loader/cleanup-entries.ts +0 -137
- package/src/loader/fetch-collection.ts +0 -177
- package/src/loader/fetch-entry.ts +0 -83
- package/src/loader/handle-realtime-updates.ts +0 -56
- package/src/loader/live-collection-loader.ts +0 -51
- package/src/loader/live-entry-loader.ts +0 -38
- package/src/loader/load-entries.ts +0 -52
- package/src/loader/loader.ts +0 -77
- package/src/loader/parse-entry.ts +0 -90
- package/src/loader/parse-live-entry.ts +0 -66
- package/src/pocketbase-loader.ts +0 -98
- package/src/schema/generate-schema.ts +0 -200
- package/src/schema/generate-type.ts +0 -23
- package/src/schema/get-remote-schema.ts +0 -43
- package/src/schema/parse-schema.ts +0 -170
- package/src/schema/read-local-schema.ts +0 -46
- package/src/schema/transform-files.ts +0 -67
- package/src/tsconfig.json +0 -7
- package/src/types/errors.ts +0 -19
- package/src/types/pocketbase-api-response.type.ts +0 -40
- package/src/types/pocketbase-entry.type.ts +0 -24
- package/src/types/pocketbase-live-loader-filter.type.ts +0 -55
- package/src/types/pocketbase-loader-options.type.ts +0 -146
- package/src/types/pocketbase-schema.type.ts +0 -101
- package/src/utils/combine-fields-for-request.ts +0 -55
- package/src/utils/create-token-promise.ts +0 -25
- package/src/utils/extract-field-names.ts +0 -15
- package/src/utils/format-fields.ts +0 -66
- package/src/utils/get-superuser-token.ts +0 -76
- package/src/utils/is-realtime-data.ts +0 -34
- package/src/utils/should-refresh.ts +0 -37
- package/src/utils/slugify.ts +0 -21
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LiveCollectionError,
|
|
3
|
-
LiveEntryNotFoundError
|
|
4
|
-
} from "astro/content/runtime";
|
|
5
|
-
import { PocketBaseAuthenticationError } from "../types/errors";
|
|
6
|
-
import { pocketBaseErrorResponse } from "../types/pocketbase-api-response.type";
|
|
7
|
-
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
8
|
-
import { pocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
9
|
-
import type { PocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
10
|
-
import { combineFieldsForRequest } from "../utils/combine-fields-for-request";
|
|
11
|
-
import { formatFields } from "../utils/format-fields";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Retrieves a specific entry from a PocketBase collection using its ID and loader options.
|
|
15
|
-
*/
|
|
16
|
-
export async function fetchEntry<TEntry extends PocketBaseEntry>(
|
|
17
|
-
id: string,
|
|
18
|
-
options: PocketBaseLiveLoaderOptions,
|
|
19
|
-
token: string | undefined
|
|
20
|
-
): Promise<TEntry> {
|
|
21
|
-
// Build the URL for the entry endpoint
|
|
22
|
-
const entryUrl = new URL(
|
|
23
|
-
`api/collections/${options.collectionName}/records/${id}`,
|
|
24
|
-
options.url
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
// Add fields parameter if specified
|
|
28
|
-
const fieldsArray = formatFields(options.fields);
|
|
29
|
-
const combinedFields = combineFieldsForRequest(fieldsArray, options);
|
|
30
|
-
if (combinedFields) {
|
|
31
|
-
entryUrl.searchParams.set("fields", combinedFields.join(","));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Create the headers for the request to append the token (if available)
|
|
35
|
-
const entryHeaders = new Headers();
|
|
36
|
-
if (token) {
|
|
37
|
-
entryHeaders.set("Authorization", token);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Fetch the entry from the collection
|
|
41
|
-
const entryRequest = await fetch(entryUrl.href, {
|
|
42
|
-
headers: entryHeaders
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// If the request was not successful, return an error
|
|
46
|
-
if (!entryRequest.ok) {
|
|
47
|
-
// If the entry is locked, a superuser token is required
|
|
48
|
-
if (entryRequest.status === 403) {
|
|
49
|
-
if (
|
|
50
|
-
options.superuserCredentials &&
|
|
51
|
-
"impersonateToken" in options.superuserCredentials
|
|
52
|
-
) {
|
|
53
|
-
throw new PocketBaseAuthenticationError(
|
|
54
|
-
options.collectionName,
|
|
55
|
-
"The given impersonate token is not valid."
|
|
56
|
-
);
|
|
57
|
-
} else {
|
|
58
|
-
throw new PocketBaseAuthenticationError(
|
|
59
|
-
options.collectionName,
|
|
60
|
-
"The entry is not accessible without superuser rights. Please provide superuser credentials in the config."
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (entryRequest.status === 404) {
|
|
66
|
-
throw new LiveEntryNotFoundError(options.collectionName, id);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Get the reason for the error
|
|
70
|
-
const errorResponse = pocketBaseErrorResponse.parse(
|
|
71
|
-
await entryRequest.json()
|
|
72
|
-
);
|
|
73
|
-
throw new LiveCollectionError(
|
|
74
|
-
options.collectionName,
|
|
75
|
-
errorResponse.message
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Get the data from the response
|
|
80
|
-
const response = pocketBaseEntry.parse(await entryRequest.json());
|
|
81
|
-
// oxlint-disable-next-line no-unsafe-type-assertion
|
|
82
|
-
return response as TEntry;
|
|
83
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { LoaderContext } from "astro/loaders";
|
|
2
|
-
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
3
|
-
import { isRealtimeData } from "../utils/is-realtime-data";
|
|
4
|
-
import { parseEntry } from "./parse-entry";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Handles realtime updates for the loader without making any new network requests.
|
|
8
|
-
*
|
|
9
|
-
* Returns `true` if the data was handled and no further action is needed.
|
|
10
|
-
*/
|
|
11
|
-
export async function handleRealtimeUpdates(
|
|
12
|
-
context: LoaderContext,
|
|
13
|
-
options: PocketBaseLoaderOptions
|
|
14
|
-
): Promise<boolean> {
|
|
15
|
-
// Check if a custom filter is set
|
|
16
|
-
if (options.filter) {
|
|
17
|
-
// Updating an entry directly via realtime updates is not supported when using a custom filter.
|
|
18
|
-
// This is because the filter can only be applied via the get request and is not considered in the realtime updates.
|
|
19
|
-
// Updating the entry directly would bypass the filter and could lead to inconsistent data.
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Check if data was provided via the refresh context
|
|
24
|
-
if (!context.refreshContextData?.data) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Check if the data is PocketBase realtime data
|
|
29
|
-
const data = context.refreshContextData.data;
|
|
30
|
-
if (!isRealtimeData(data)) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Check if the collection name matches the current collection
|
|
35
|
-
if (data.record.collectionName !== options.collectionName) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Handle deleted entry
|
|
40
|
-
if (data.action === "delete") {
|
|
41
|
-
context.logger.info("Removing deleted entry");
|
|
42
|
-
context.store.delete(data.record.id);
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Handle updated or new entry
|
|
47
|
-
if (data.action === "update") {
|
|
48
|
-
context.logger.info("Updating outdated entry");
|
|
49
|
-
} else {
|
|
50
|
-
context.logger.info("Creating new entry");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Parse the entry and store
|
|
54
|
-
await parseEntry(data.record, context, options);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { LiveDataCollection, LiveDataEntry } from "astro";
|
|
2
|
-
import { LiveCollectionError } from "astro/content/runtime";
|
|
3
|
-
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
4
|
-
import type { PocketBaseLiveLoaderCollectionFilter } from "../types/pocketbase-live-loader-filter.type";
|
|
5
|
-
import type { PocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
6
|
-
import { fetchCollection } from "./fetch-collection";
|
|
7
|
-
import { parseLiveEntry } from "./parse-live-entry";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Loads and parses a PocketBase collection for live data, returning entries or an error.
|
|
11
|
-
*/
|
|
12
|
-
export async function liveCollectionLoader<TEntry extends PocketBaseEntry>(
|
|
13
|
-
collectionFilter: PocketBaseLiveLoaderCollectionFilter | undefined,
|
|
14
|
-
options: PocketBaseLiveLoaderOptions,
|
|
15
|
-
token: string | undefined
|
|
16
|
-
): Promise<LiveDataCollection<TEntry> | { error: LiveCollectionError }> {
|
|
17
|
-
const entries: Array<LiveDataEntry<TEntry>> = [];
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
await fetchCollection<TEntry>(
|
|
21
|
-
options,
|
|
22
|
-
async (chunk) => {
|
|
23
|
-
entries.push(...chunk.map((entry) => parseLiveEntry(entry, options)));
|
|
24
|
-
},
|
|
25
|
-
token,
|
|
26
|
-
collectionFilter
|
|
27
|
-
);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
if (error instanceof LiveCollectionError) {
|
|
30
|
-
return { error };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (error instanceof Error) {
|
|
34
|
-
return {
|
|
35
|
-
error: new LiveCollectionError(
|
|
36
|
-
options.collectionName,
|
|
37
|
-
error.message,
|
|
38
|
-
error
|
|
39
|
-
)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
error: new LiveCollectionError(options.collectionName, String(error))
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
entries
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { LiveDataEntry } from "astro";
|
|
2
|
-
import { LiveCollectionError } from "astro/content/runtime";
|
|
3
|
-
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
4
|
-
import type { PocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
5
|
-
import { fetchEntry } from "./fetch-entry";
|
|
6
|
-
import { parseLiveEntry } from "./parse-live-entry";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Loads and parses a single PocketBase entry for live data, returning the entry or an error.
|
|
10
|
-
*/
|
|
11
|
-
export async function liveEntryLoader<TEntry extends PocketBaseEntry>(
|
|
12
|
-
id: string,
|
|
13
|
-
options: PocketBaseLiveLoaderOptions,
|
|
14
|
-
token: string | undefined
|
|
15
|
-
): Promise<LiveDataEntry<TEntry> | { error: LiveCollectionError }> {
|
|
16
|
-
try {
|
|
17
|
-
const entry = await fetchEntry<TEntry>(id, options, token);
|
|
18
|
-
return parseLiveEntry(entry, options);
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (error instanceof LiveCollectionError) {
|
|
21
|
-
return { error };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (error instanceof Error) {
|
|
25
|
-
return {
|
|
26
|
-
error: new LiveCollectionError(
|
|
27
|
-
options.collectionName,
|
|
28
|
-
error.message,
|
|
29
|
-
error
|
|
30
|
-
)
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
error: new LiveCollectionError(options.collectionName, String(error))
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { LoaderContext } from "astro/loaders";
|
|
2
|
-
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
3
|
-
import { fetchCollection } from "./fetch-collection";
|
|
4
|
-
import { parseEntry } from "./parse-entry";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Load (modified) entries from a PocketBase collection.
|
|
8
|
-
*
|
|
9
|
-
* @param options Options for the loader.
|
|
10
|
-
* @param context Context of the loader.
|
|
11
|
-
* @param superuserToken Superuser token to access all resources.
|
|
12
|
-
* @param lastModified Date of the last fetch to only update changed entries.
|
|
13
|
-
*/
|
|
14
|
-
export async function loadEntries(
|
|
15
|
-
options: PocketBaseLoaderOptions,
|
|
16
|
-
context: LoaderContext,
|
|
17
|
-
superuserToken: string | undefined,
|
|
18
|
-
lastModified: string | undefined
|
|
19
|
-
): Promise<void> {
|
|
20
|
-
// Log the fetching of the entries
|
|
21
|
-
context.logger.info(
|
|
22
|
-
`Fetching${lastModified ? " modified" : ""} data${
|
|
23
|
-
lastModified ? ` starting at ${lastModified}` : ""
|
|
24
|
-
}${superuserToken ? " as superuser" : ""}`
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
let numEntries = 0;
|
|
28
|
-
await fetchCollection(
|
|
29
|
-
options,
|
|
30
|
-
async (entries) => {
|
|
31
|
-
// Parse and store the entries
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
await parseEntry(entry, context, options);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
numEntries += entries.length;
|
|
37
|
-
},
|
|
38
|
-
superuserToken,
|
|
39
|
-
{
|
|
40
|
-
lastModified
|
|
41
|
-
}
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
// Log the number of fetched entries
|
|
45
|
-
if (lastModified) {
|
|
46
|
-
context.logger.info(
|
|
47
|
-
`Updated ${numEntries}/${context.store.keys().length} entries.`
|
|
48
|
-
);
|
|
49
|
-
} else {
|
|
50
|
-
context.logger.info(`Fetched ${numEntries} entries.`);
|
|
51
|
-
}
|
|
52
|
-
}
|
package/src/loader/loader.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { LoaderContext } from "astro/loaders";
|
|
2
|
-
import packageJson from "../../package.json";
|
|
3
|
-
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
4
|
-
import { shouldRefresh } from "../utils/should-refresh";
|
|
5
|
-
import { cleanupEntries } from "./cleanup-entries";
|
|
6
|
-
import { handleRealtimeUpdates } from "./handle-realtime-updates";
|
|
7
|
-
import { loadEntries } from "./load-entries";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Load entries from a PocketBase collection.
|
|
11
|
-
*/
|
|
12
|
-
export async function loader(
|
|
13
|
-
context: LoaderContext,
|
|
14
|
-
options: PocketBaseLoaderOptions,
|
|
15
|
-
token: string | undefined
|
|
16
|
-
): Promise<void> {
|
|
17
|
-
context.logger.label = `pocketbase-loader:${options.collectionName}`;
|
|
18
|
-
|
|
19
|
-
// Check if the collection should be refreshed.
|
|
20
|
-
const refresh = shouldRefresh(
|
|
21
|
-
context.refreshContextData,
|
|
22
|
-
options.collectionName
|
|
23
|
-
);
|
|
24
|
-
if (refresh === "skip") {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Handle realtime updates
|
|
29
|
-
const handled = await handleRealtimeUpdates(context, options);
|
|
30
|
-
if (handled) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Get the date of the last fetch to only update changed entries.
|
|
35
|
-
let lastModified = context.meta.get("last-modified");
|
|
36
|
-
|
|
37
|
-
// Force a full update if the refresh is forced
|
|
38
|
-
if (refresh === "force") {
|
|
39
|
-
lastModified = undefined;
|
|
40
|
-
context.store.clear();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Check if the version has changed to force an update
|
|
44
|
-
const lastVersion = context.meta.get("version");
|
|
45
|
-
if (lastVersion !== packageJson.version) {
|
|
46
|
-
if (lastVersion) {
|
|
47
|
-
context.logger.info(
|
|
48
|
-
`PocketBase loader was updated from ${lastVersion} to ${packageJson.version}. All entries will be loaded again.`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Disable incremental builds and clear the store
|
|
53
|
-
lastModified = undefined;
|
|
54
|
-
context.store.clear();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Disable incremental builds if no updated field is provided
|
|
58
|
-
if (!options.updatedField) {
|
|
59
|
-
context.logger.info(
|
|
60
|
-
`No "updatedField" was provided. Incremental builds are disabled.`
|
|
61
|
-
);
|
|
62
|
-
lastModified = undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (context.store.keys().length > 0) {
|
|
66
|
-
// Cleanup entries that are no longer in the collection
|
|
67
|
-
await cleanupEntries(options, context, token);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Load the (modified) entries
|
|
71
|
-
await loadEntries(options, context, token, lastModified);
|
|
72
|
-
|
|
73
|
-
// Set the last modified date to the current date
|
|
74
|
-
context.meta.set("last-modified", new Date().toISOString().replace("T", " "));
|
|
75
|
-
|
|
76
|
-
context.meta.set("version", packageJson.version);
|
|
77
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { LoaderContext } from "astro/loaders";
|
|
2
|
-
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
3
|
-
import type { PocketBaseLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
4
|
-
import { slugify } from "../utils/slugify";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Parse an entry from PocketBase to match the schema and store it in the store.
|
|
8
|
-
*
|
|
9
|
-
* @param entry Entry to parse.
|
|
10
|
-
* @param context Context of the loader.
|
|
11
|
-
* @param idField Field to use as id for the entry.
|
|
12
|
-
* If not provided, the id of the entry will be used.
|
|
13
|
-
* @param contentFields Field(s) to use as content for the entry.
|
|
14
|
-
* If multiple fields are used, they will be concatenated and wrapped in `<section>` elements.
|
|
15
|
-
*/
|
|
16
|
-
export async function parseEntry(
|
|
17
|
-
entry: PocketBaseEntry,
|
|
18
|
-
{ generateDigest, parseData, store, logger }: LoaderContext,
|
|
19
|
-
{ idField, contentFields, updatedField }: PocketBaseLoaderOptions
|
|
20
|
-
): Promise<void> {
|
|
21
|
-
let id = entry.id;
|
|
22
|
-
if (idField) {
|
|
23
|
-
// Get the custom ID of the entry if it exists
|
|
24
|
-
const customEntryId = entry[idField];
|
|
25
|
-
|
|
26
|
-
if (!customEntryId) {
|
|
27
|
-
logger.warn(
|
|
28
|
-
`The entry "${id}" does not have a value for field ${idField}. Using the default ID instead.`
|
|
29
|
-
);
|
|
30
|
-
} else {
|
|
31
|
-
id = slugify(`${customEntryId}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const oldEntry = store.get(id);
|
|
36
|
-
if (oldEntry && oldEntry.data.id !== entry.id) {
|
|
37
|
-
logger.warn(
|
|
38
|
-
`The entry "${entry.id}" seems to be a duplicate of "${oldEntry.data.id}". Please make sure to use unique IDs in the column "${idField}".`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Parse the data to match the schema
|
|
43
|
-
// This will throw an error if the data does not match the schema
|
|
44
|
-
const data = await parseData({
|
|
45
|
-
id,
|
|
46
|
-
data: entry
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Get the updated date of the entry
|
|
50
|
-
let updated: string | undefined;
|
|
51
|
-
if (updatedField) {
|
|
52
|
-
updated = `${entry[updatedField]}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Generate a digest for the entry
|
|
56
|
-
// If no updated date is available, the digest will be generated from the whole entry
|
|
57
|
-
const digest = generateDigest(updated ?? entry);
|
|
58
|
-
|
|
59
|
-
if (!contentFields) {
|
|
60
|
-
// Store the entry
|
|
61
|
-
store.set({
|
|
62
|
-
id,
|
|
63
|
-
data,
|
|
64
|
-
digest
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Generate the content for the entry
|
|
70
|
-
let content: string;
|
|
71
|
-
if (typeof contentFields === "string") {
|
|
72
|
-
// Only one field is used as content
|
|
73
|
-
content = `${entry[contentFields]}`;
|
|
74
|
-
} else {
|
|
75
|
-
// Multiple fields are used as content, wrap each block in a section and concatenate them
|
|
76
|
-
content = contentFields
|
|
77
|
-
.map((field) => `<section id="${field}">${entry[field]}</section>`)
|
|
78
|
-
.join("");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Store the entry
|
|
82
|
-
store.set({
|
|
83
|
-
id,
|
|
84
|
-
data,
|
|
85
|
-
digest,
|
|
86
|
-
rendered: {
|
|
87
|
-
html: content
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { LiveDataEntry } from "astro";
|
|
2
|
-
import { LiveCollectionValidationError } from "astro/content/runtime";
|
|
3
|
-
import { z } from "astro/zod";
|
|
4
|
-
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
|
|
5
|
-
import type { PocketBaseLiveLoaderOptions } from "../types/pocketbase-loader-options.type";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Converts a PocketBase entry into a LiveDataEntry for Astro, extracting content and cache metadata.
|
|
9
|
-
*/
|
|
10
|
-
export function parseLiveEntry<TEntry extends PocketBaseEntry>(
|
|
11
|
-
entry: TEntry,
|
|
12
|
-
options: PocketBaseLiveLoaderOptions
|
|
13
|
-
): LiveDataEntry<TEntry> {
|
|
14
|
-
// Build a cache tag
|
|
15
|
-
const tag = `${options.collectionName}-${entry.id}`;
|
|
16
|
-
|
|
17
|
-
let lastModified: Date | undefined = undefined;
|
|
18
|
-
// If an updated field is provided and the entry has a valid date value,
|
|
19
|
-
// use it as the last modified date cache hint
|
|
20
|
-
if (options.updatedField && entry[options.updatedField]) {
|
|
21
|
-
const value = `${entry[options.updatedField]}`;
|
|
22
|
-
const date = z.coerce.date().safeParse(value);
|
|
23
|
-
if (!date.success) {
|
|
24
|
-
throw new LiveCollectionValidationError(
|
|
25
|
-
options.collectionName,
|
|
26
|
-
entry.id,
|
|
27
|
-
date.error
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
lastModified = date.data;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!options.contentFields) {
|
|
34
|
-
return {
|
|
35
|
-
id: entry.id,
|
|
36
|
-
data: entry,
|
|
37
|
-
cacheHint: {
|
|
38
|
-
tags: [tag],
|
|
39
|
-
lastModified
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let content: string;
|
|
45
|
-
if (typeof options.contentFields === "string") {
|
|
46
|
-
// If a single content field is provided, use it directly
|
|
47
|
-
content = `${entry[options.contentFields]}`;
|
|
48
|
-
} else {
|
|
49
|
-
// If multiple content fields are provided, concatenate them with `<section>` tags
|
|
50
|
-
content = options.contentFields
|
|
51
|
-
.map((field) => `<section id="${field}">${entry[field]}</section>`)
|
|
52
|
-
.join("");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
id: entry.id,
|
|
57
|
-
data: entry,
|
|
58
|
-
rendered: {
|
|
59
|
-
html: content
|
|
60
|
-
},
|
|
61
|
-
cacheHint: {
|
|
62
|
-
tags: [tag],
|
|
63
|
-
lastModified
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
package/src/pocketbase-loader.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { LiveDataCollection, LiveDataEntry } from "astro";
|
|
2
|
-
import type { LiveCollectionError } from "astro/content/runtime";
|
|
3
|
-
import type { LiveLoader, Loader, LoaderContext } from "astro/loaders";
|
|
4
|
-
import { liveCollectionLoader } from "./loader/live-collection-loader";
|
|
5
|
-
import { liveEntryLoader } from "./loader/live-entry-loader";
|
|
6
|
-
import { loader } from "./loader/loader";
|
|
7
|
-
import { generateSchema } from "./schema/generate-schema";
|
|
8
|
-
import { generateType } from "./schema/generate-type";
|
|
9
|
-
import type { PocketBaseEntry } from "./types/pocketbase-entry.type";
|
|
10
|
-
import type {
|
|
11
|
-
PocketBaseLiveLoaderCollectionFilter,
|
|
12
|
-
PocketBaseLiveLoaderEntryFilter
|
|
13
|
-
} from "./types/pocketbase-live-loader-filter.type";
|
|
14
|
-
import type {
|
|
15
|
-
PocketBaseLiveLoaderOptions,
|
|
16
|
-
PocketBaseLoaderOptions
|
|
17
|
-
} from "./types/pocketbase-loader-options.type";
|
|
18
|
-
import { createTokenPromise } from "./utils/create-token-promise";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Loader for collections stored in PocketBase.
|
|
22
|
-
*
|
|
23
|
-
* @param options Options for the loader. See {@link PocketBaseLoaderOptions} for more details.
|
|
24
|
-
*/
|
|
25
|
-
// oxlint-disable-next-line explicit-module-boundary-types
|
|
26
|
-
export function pocketbaseLoader(options: PocketBaseLoaderOptions) {
|
|
27
|
-
// Create shared promise for the superuser token, which can be reused
|
|
28
|
-
const tokenPromise = createTokenPromise(options);
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
name: "pocketbase-loader",
|
|
32
|
-
load: async (context: LoaderContext): Promise<void> => {
|
|
33
|
-
if (options.experimental?.liveTypesOnly) {
|
|
34
|
-
context.logger.label = `pocketbase-loader:${options.collectionName}`;
|
|
35
|
-
context.logger.info(
|
|
36
|
-
"Experimental live types only mode enabled. No data will be loaded, only types will be generated."
|
|
37
|
-
);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const token = await tokenPromise;
|
|
42
|
-
|
|
43
|
-
// Load the entries from the collection
|
|
44
|
-
await loader(context, options, token);
|
|
45
|
-
},
|
|
46
|
-
createSchema: async () => {
|
|
47
|
-
const token = await tokenPromise;
|
|
48
|
-
|
|
49
|
-
// Generate the schema for the collection according to the API
|
|
50
|
-
const schema = await generateSchema(options, token);
|
|
51
|
-
const types = generateType(schema);
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
schema,
|
|
55
|
-
types
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
} satisfies Loader;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Live loader for collections stored in PocketBase.
|
|
63
|
-
*
|
|
64
|
-
* @param options Options for the live loader. See {@link PocketBaseLiveLoaderOptions} for more details.
|
|
65
|
-
*/
|
|
66
|
-
export function pocketbaseLiveLoader<TEntry extends PocketBaseEntry>(
|
|
67
|
-
options: PocketBaseLiveLoaderOptions
|
|
68
|
-
): LiveLoader<
|
|
69
|
-
TEntry,
|
|
70
|
-
PocketBaseLiveLoaderEntryFilter,
|
|
71
|
-
PocketBaseLiveLoaderCollectionFilter,
|
|
72
|
-
LiveCollectionError
|
|
73
|
-
> {
|
|
74
|
-
// Create shared promise for the superuser token, which can be reused
|
|
75
|
-
const tokenPromise = createTokenPromise(options);
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
name: "pocketbase-live-loader",
|
|
79
|
-
loadCollection: async ({
|
|
80
|
-
filter
|
|
81
|
-
}): Promise<
|
|
82
|
-
LiveDataCollection<TEntry> | { error: LiveCollectionError }
|
|
83
|
-
> => {
|
|
84
|
-
const token = await tokenPromise;
|
|
85
|
-
|
|
86
|
-
// Load entries from the collection
|
|
87
|
-
return liveCollectionLoader<TEntry>(filter, options, token);
|
|
88
|
-
},
|
|
89
|
-
loadEntry: async ({
|
|
90
|
-
filter
|
|
91
|
-
}): Promise<LiveDataEntry<TEntry> | { error: LiveCollectionError }> => {
|
|
92
|
-
const token = await tokenPromise;
|
|
93
|
-
|
|
94
|
-
// Load a single entry from the collection
|
|
95
|
-
return liveEntryLoader<TEntry>(filter.id, options, token);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|