diff --git a/packages/host/tests/integration/components/prerendered-card-search-test.gts b/packages/host/tests/integration/components/prerendered-card-search-test.gts
index ccbefcb987..bd2ea02147 100644
--- a/packages/host/tests/integration/components/prerendered-card-search-test.gts
+++ b/packages/host/tests/integration/components/prerendered-card-search-test.gts
@@ -328,6 +328,7 @@ module(`Integration | prerendered-card-search`, function (hooks) {
'person.gts': { PersonField },
'post.gts': { Post },
'publisher.gts': { Publisher },
+ 'sample-doc.md': '# Test Document\n\nSome content here.',
...sampleCards,
},
}));
@@ -836,4 +837,48 @@ module(`Integration | prerendered-card-search`, function (hooks) {
.dom('[data-test-meta-page-total="5"]')
.exists('meta.page.total remains correct on last page');
});
+
+ test(`can search for files using a FileDef type filter`, async function (assert) {
+ let query: Query = {
+ filter: {
+ type: {
+ module: `${baseRealm.url}markdown-file-def`,
+ name: 'MarkdownDef',
+ },
+ },
+ };
+ let realms = [testRealmURL];
+
+ await render(
+
+
+ <:loading>
+ Loading...
+
+ <:response as |cards|>
+ {{#each cards as |card|}}
+
+ {{/each}}
+
+ <:meta as |meta|>
+
+
+
+ ,
+ );
+ await waitFor('#ember-testing > [data-test-boxel-card-container]');
+ assert
+ .dom('#ember-testing > [data-test-boxel-card-container]')
+ .exists({ count: 1 }, 'one markdown file result is rendered');
+ assert
+ .dom('#ember-testing > [data-test-boxel-card-container]')
+ .containsText('Test Document', 'markdown file title appears in rendered html');
+ assert
+ .dom('[data-test-meta-page-total="1"]')
+ .exists('meta.page.total is correct for file-meta search');
+ });
});
diff --git a/packages/realm-server/tests/search-prerendered-test.ts b/packages/realm-server/tests/search-prerendered-test.ts
index b0881e8099..fa8675f913 100644
--- a/packages/realm-server/tests/search-prerendered-test.ts
+++ b/packages/realm-server/tests/search-prerendered-test.ts
@@ -973,6 +973,200 @@ module(basename(__filename), function () {
});
});
+ module('file-meta queries', function (hooks) {
+ setupPermissionedRealm(hooks, {
+ realmURL,
+ permissions: {
+ '*': ['read'],
+ },
+ fileSystem: {
+ 'sample.md': `# Test Document\n\nThis is a sample markdown file for testing.`,
+ 'notes.md': `# Notes\n\nSome notes content here.`,
+ 'readme.txt': 'Plain text file contents.',
+ 'person.gts': `
+ import { contains, field, CardDef, Component } from "https://cardstack.com/base/card-api";
+ import StringField from "https://cardstack.com/base/string";
+
+ export class Person extends CardDef {
+ @field firstName = contains(StringField);
+ static embedded = class Embedded extends Component {
+
+ Embedded Card Person: <@fields.firstName/>
+
+ }
+ }
+ `,
+ 'john.json': {
+ data: {
+ attributes: {
+ firstName: 'John',
+ },
+ meta: {
+ adoptsFrom: {
+ module: './person',
+ name: 'Person',
+ },
+ },
+ },
+ },
+ },
+ onRealmSetup,
+ });
+
+ test('returns prerendered file results for FileDef type filter', async function (assert) {
+ let query: Query & { prerenderedHtmlFormat: string } = {
+ filter: {
+ type: {
+ module: `https://cardstack.com/base/file-api`,
+ name: 'FileDef',
+ },
+ },
+ prerenderedHtmlFormat: 'embedded',
+ };
+ let response = await request
+ .post(searchPath)
+ .set('Accept', 'application/vnd.card+json')
+ .set('X-HTTP-Method-Override', 'QUERY')
+ .send(query);
+
+ assert.strictEqual(response.status, 200, 'HTTP 200 status');
+ let json = response.body;
+
+ assert.true(
+ json.data.length >= 2,
+ 'at least the markdown files are returned',
+ );
+
+ json.data.forEach(
+ (item: { type: string; attributes: { html: string } }) => {
+ assert.strictEqual(
+ item.type,
+ 'prerendered-card',
+ 'result type is prerendered-card',
+ );
+ assert.ok(
+ item.attributes.html,
+ 'result has non-empty html attribute',
+ );
+ },
+ );
+
+ assert.true(
+ json.meta.page.total >= 2,
+ 'total count includes file results',
+ );
+ });
+
+ test('returns prerendered file results for MarkdownDef subclass filter', async function (assert) {
+ let query: Query & { prerenderedHtmlFormat: string } = {
+ filter: {
+ type: {
+ module: `https://cardstack.com/base/markdown-file-def`,
+ name: 'MarkdownDef',
+ },
+ },
+ prerenderedHtmlFormat: 'embedded',
+ };
+ let response = await request
+ .post(searchPath)
+ .set('Accept', 'application/vnd.card+json')
+ .set('X-HTTP-Method-Override', 'QUERY')
+ .send(query);
+
+ assert.strictEqual(response.status, 200, 'HTTP 200 status');
+ let json = response.body;
+
+ assert.strictEqual(
+ json.data.length,
+ 2,
+ 'only markdown files are returned',
+ );
+
+ // MarkdownDef uses MarkdownFilePreview template which renders
+ // an article with class 'markdown-file-preview'
+ let htmls = json.data.map((item: { attributes: { html: string } }) =>
+ item.attributes.html.replace(/\s+/g, ' '),
+ );
+ assert.true(
+ htmls.some((html: string) => html.includes('Test Document')),
+ 'sample.md title appears in prerendered html',
+ );
+ assert.true(
+ htmls.some((html: string) => html.includes('Notes')),
+ 'notes.md title appears in prerendered html',
+ );
+
+ assert.strictEqual(
+ json.meta.page.total,
+ 2,
+ 'total count matches markdown files',
+ );
+ });
+
+ test('returns fitted format for file results', async function (assert) {
+ let query: Query & { prerenderedHtmlFormat: string } = {
+ filter: {
+ type: {
+ module: `https://cardstack.com/base/markdown-file-def`,
+ name: 'MarkdownDef',
+ },
+ },
+ prerenderedHtmlFormat: 'fitted',
+ };
+ let response = await request
+ .post(searchPath)
+ .set('Accept', 'application/vnd.card+json')
+ .set('X-HTTP-Method-Override', 'QUERY')
+ .send(query);
+
+ assert.strictEqual(response.status, 200, 'HTTP 200 status');
+ let json = response.body;
+
+ assert.strictEqual(
+ json.data.length,
+ 2,
+ 'markdown files are returned',
+ );
+
+ json.data.forEach(
+ (item: { type: string; attributes: { html: string } }) => {
+ assert.strictEqual(
+ item.type,
+ 'prerendered-card',
+ 'result type is prerendered-card',
+ );
+ assert.ok(item.attributes.html, 'fitted html is non-empty');
+ },
+ );
+ });
+
+ test('file-meta query does not return card instances', async function (assert) {
+ let query: Query & { prerenderedHtmlFormat: string } = {
+ filter: {
+ type: {
+ module: `https://cardstack.com/base/markdown-file-def`,
+ name: 'MarkdownDef',
+ },
+ },
+ prerenderedHtmlFormat: 'embedded',
+ };
+ let response = await request
+ .post(searchPath)
+ .set('Accept', 'application/vnd.card+json')
+ .set('X-HTTP-Method-Override', 'QUERY')
+ .send(query);
+
+ let json = response.body;
+
+ // Verify no card instances (john.json) are in the results
+ let ids = json.data.map((item: { id: string }) => item.id);
+ assert.false(
+ ids.some((id: string) => id.includes('john.json')),
+ 'card instance john.json is not in file-meta results',
+ );
+ });
+ });
+
module('permissioned realm', function (hooks) {
setupPermissionedRealm(hooks, {
realmURL,
diff --git a/packages/runtime-common/index-query-engine.ts b/packages/runtime-common/index-query-engine.ts
index 62798dba18..8883ed8a28 100644
--- a/packages/runtime-common/index-query-engine.ts
+++ b/packages/runtime-common/index-query-engine.ts
@@ -584,6 +584,7 @@ export class IndexQueryEngine {
realmURL: URL,
{ filter, sort, page }: Query,
opts: QueryOptions = { includeErrors: true },
+ entryType: 'instance' | 'file' = 'instance',
): Promise<{
prerenderedCards: PrerenderedCard[];
scopedCssUrls: string[];
@@ -617,7 +618,7 @@ export class IndexQueryEngine {
' as used_render_type,',
'ANY_VALUE(deps) as deps',
],
- 'instance',
+ entryType,
)) as {
meta: QueryResultsMeta;
results: (Partial & {
diff --git a/packages/runtime-common/realm-index-query-engine.ts b/packages/runtime-common/realm-index-query-engine.ts
index b4aa522579..f4256086b5 100644
--- a/packages/runtime-common/realm-index-query-engine.ts
+++ b/packages/runtime-common/realm-index-query-engine.ts
@@ -277,10 +277,12 @@ export class RealmIndexQueryEngine {
}
async searchPrerendered(query: Query, opts?: Options) {
+ let isFileMetaQuery = await this.queryTargetsFileMeta(query.filter, opts);
let results = await this.#indexQueryEngine.searchPrerendered(
new URL(this.#realm.url),
query,
opts,
+ isFileMetaQuery ? 'file' : 'instance',
);
return results;