#custom content loader

18 messages · Page 1 of 1 (latest)

near vessel
#

this is my loader code

const kwords = defineCollection({
    loader: async () => {
        const kws = import.meta.glob('./content/kw-x-doc/*.json', { import : 'default' });
        const result : Array<{ id : string }> = [];
        for (const id in kws) 
            result.push({ 
                id : id.slice(19,-5).toLocaleLowerCase(),
                ...await kws[id]() as Record<string, number[]>
            });
        return result;
    }
});

this works moslty as expected; however, the collection entry data contains the id property, which is not expected. The collection entry is expected to be a Record<string, number[]> and not a { id: string } & Record<string, number[]>
why does this happend and how could this be prevented?

naive ibexBOT
#

Hi @near vessel. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the information provided in the knowledge sources, I can explain why this is happening and suggest a way to prevent it.

The issue you're experiencing is related to how Astro's content collections handle entry IDs. According to the Content Collections API Reference, each entry in a collection must have a unique id property. This is a requirement for all content entries, regardless of how they are loaded.

When you return an array of objects from your custom loader, Astro automatically includes the id property in the entry data. This is why you're seeing the id property in your collection entries, even though you didn't explicitly include it in your Record<string, number[]> type.

To prevent this and ensure that your collection entries only contain the data you expect, you can use the DataEntry type from the Content Loader API. Here's how you can modify your loader to achieve this:

import { defineCollection } from 'astro:content';
import type { DataEntry } from 'astro/content';

const kwords = defineCollection({
    loader: async () => {
        const kws = import.meta.glob('./content/kw-x-doc/*.json', { import: 'default' });
        const result: DataEntry[] = [];
        for (const path in kws) {
            const id = path.slice(19, -5).toLowerCase();
            const data = await kws[path]() as Record<string, number[]>;
            result.push({
                id,
                data,
            });
        }
        return result;
    }
});

In this modified version:

  1. We import the DataEntry type from astro/content.
  2. We create objects that conform to the DataEntry structure, which separates the id from the actual data.
  3. The data property contains your Record<string, number[]>, while the id is kept separate.
#

This approach ensures that when you query your collection, you'll get entries where the data property contains only your Record<string, number[]>, without the id mixed in.

This solution is based on the information from the Astro Content Loader API documentation, which describes the structure of the DataEntry type.

By using this approach, you can prevent the id from being included in your entry data while still satisfying Astro's requirement for unique entry identifiers.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@near vessel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

near vessel
#

@naive ibex now it is worse, whenever entry.data is accessed, the type is { id: string, data: t_expected }. In other words:

await getCollection("kword") // => { id: string; collection: string; data: { id: string, data: t_expected }}

tip: t_expected = Record<string, number[]>

naive ibexBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

I apologize for the confusion in my previous answer. You're right, and I understand the issue better now. Let's address this problem with a more accurate solution based on the Astro documentation.

The behavior you're experiencing is related to how Astro handles the DataEntry type internally. To solve this issue and get the expected type without the nested data property, we need to modify our approach. Here's a solution that should work:

import { defineCollection, type DataEntry } from 'astro:content';

type KwordEntry = Record<string, number[]>;

const kwords = defineCollection({
    type: 'data', // This is important
    schema: {} as KwordEntry, // This helps TypeScript infer the correct type
    loader: async () => {
        const kws = import.meta.glob('./content/kw-x-doc/*.json', { import: 'default' });
        const result: Record<string, KwordEntry> = {};
        for (const path in kws) {
            const id = path.slice(19, -5).toLowerCase();
            const data = await kws[path]() as KwordEntry;
            result[id] = data;
        }
        return result;
    }
});

This solution is based on the information from the Content Collections API Reference and the Legacy Content Collections documentation.

Key points:

  1. We define the collection as type 'data'. This is crucial for handling non-content collections.
  2. We use a schema that matches our expected type. Even though it's empty, it helps TypeScript infer the correct type.
  3. The loader returns an object where the keys are the IDs and the values are the data entries.

With this setup, when you use getCollection("kword"), you should get entries of type:

#
{
  id: string;
  collection: string;
  data: Record<string, number[]>;
}

This matches your expected type t_expected without the nested data property.

If you still encounter issues, it might be related to how Astro is inferring types for your collection. In that case, you might need to explicitly type your getCollection call:

import type { CollectionEntry } from 'astro:content';

const entries = await getCollection('kword') as CollectionEntry<'kword'>[];

This should ensure that the data property of each entry is correctly typed as Record<string, number[]>.

Content Collections API Reference
Legacy Content Collections documentation

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@near vessel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

near vessel
#

@naive ibex the compiler complains about expecting an array and receiving an object

naive ibexBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

I apologize for the confusion in my previous answer. You're right, and this error occurs because the loader is expected to return an array, not an object. Let's correct this based on the information from the Content Collections API Reference and the Content Loader API documentation.

Here's a corrected version of the loader that should resolve the compiler error:

import { defineCollection } from 'astro:content';

type KwordEntry = Record<string, number[]>;

const kwords = defineCollection({
    type: 'data',
    schema: {} as KwordEntry,
    loader: async () => {
        const kws = import.meta.glob('./content/kw-x-doc/*.json', { import: 'default' });
        const result: Array<{ id: string } & KwordEntry> = [];
        for (const path in kws) {
            const id = path.slice(19, -5).toLowerCase();
            const data = await kws[path]() as KwordEntry;
            result.push({ id, ...data });
        }
        return result;
    }
});

In this corrected version:

  1. We're returning an array of entries, each containing an id and the data from the JSON file.
  2. The id is included in each entry object, as required by Astro's content collections.
  3. We spread the data from each JSON file into the entry object, so it's not nested under a data property.

This approach should satisfy the compiler's expectation of an array return type from the loader. When you use getCollection("kword"), you should now get an array of entries, each with the structure:

{
  id: string;
  [key: string]: number[];
}

This structure combines the required id field with your KwordEntry type, which should resolve the typing issues you were experiencing.

#

@near vessel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

near vessel
#

@naive ibex ok one more question, does the getEntry returns a Promise<entry> | undefined or a Promise<entry | undefined?

naive ibexBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To answer your question accurately, I'll refer to the Astro documentation on the getEntry() function.

According to the Content Collections API Reference, the getEntry() function returns a Promise<CollectionEntry<collection> | undefined>.

This means that getEntry() returns a Promise<entry | undefined>, not a Promise<entry> | undefined.

The exact type signature is:

(collection: string, id: string) => Promise<CollectionEntry<collection> | undefined>

This is important because it means that when you use getEntry(), you need to await the promise, and then check if the result is undefined. For example:

const entry = await getEntry('myCollection', 'myEntryId');
if (entry === undefined) {
  // Handle the case where the entry doesn't exist
} else {
  // Use the entry
}

It's worth noting that in Astro v3.0 and later, this behavior is consistent with how Astro.cookies.get(key) works, as mentioned in the Astro v3.0 upgrade guide.

Content Collections API Reference
Astro v3.0 upgrade guide

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: