diff --git a/docs/app/components/PostPage.vue b/docs/app/components/PostPage.vue
index 9f70709c6..5c6aea072 100644
--- a/docs/app/components/PostPage.vue
+++ b/docs/app/components/PostPage.vue
@@ -11,9 +11,9 @@ const { type } = defineProps({
const route = useRoute()
const siteConfig = useSiteConfig()
-const { data } = await useAsyncData(route.path, () => Promise.all([
- queryCollection('posts').path(route.path).first(),
- queryCollectionItemSurroundings('posts', route.path, { fields: ['title', 'description'] })
+const { data } = await useAsyncData(route.path, (_nuxtApp, { signal }) => Promise.all([
+ queryCollection('posts').path(route.path).first({ signal }),
+ queryCollectionItemSurroundings('posts', route.path, { fields: ['title', 'description'] }, { signal })
.where('path', 'LIKE', `/${type}%`)
.where('draft', '=', 0)
.order('date', 'DESC'),
diff --git a/docs/app/components/example/ExampleFulltextFusejs.vue b/docs/app/components/example/ExampleFulltextFusejs.vue
index ff69047ba..26cc4f6a3 100644
--- a/docs/app/components/example/ExampleFulltextFusejs.vue
+++ b/docs/app/components/example/ExampleFulltextFusejs.vue
@@ -2,7 +2,7 @@
import Fuse from 'fuse.js'
const query = ref('')
-const { data } = await useAsyncData('search-data', () => queryCollectionSearchSections('docs'))
+const { data } = await useAsyncData('search-data', (_nuxtApp, { signal }) => queryCollectionSearchSections('docs', { signal }))
const fuse = new Fuse(data.value || [], {
keys: [
diff --git a/docs/app/components/example/ExampleFulltextMiniSearch.vue b/docs/app/components/example/ExampleFulltextMiniSearch.vue
index 0ef4dceb9..8ef09f99e 100644
--- a/docs/app/components/example/ExampleFulltextMiniSearch.vue
+++ b/docs/app/components/example/ExampleFulltextMiniSearch.vue
@@ -2,7 +2,7 @@
import MiniSearch from 'minisearch'
const query = ref('')
-const { data } = await useAsyncData('search-data', () => queryCollectionSearchSections('docs'))
+const { data } = await useAsyncData('search-data', (_nuxtApp, { signal }) => queryCollectionSearchSections('docs', { signal }))
const miniSearch = new MiniSearch({
fields: ['title', 'content'],
diff --git a/docs/app/pages/blog/index.vue b/docs/app/pages/blog/index.vue
index 3c3fe15a5..134121e0f 100644
--- a/docs/app/pages/blog/index.vue
+++ b/docs/app/pages/blog/index.vue
@@ -3,16 +3,16 @@ import { titleCase } from 'scule'
const siteConfig = useSiteConfig()
-const { data: page } = await useAsyncData('blog-landing', () => queryCollection('landing').path('/blog').first())
+const { data: page } = await useAsyncData('blog-landing', (_nuxtApp, { signal }) => queryCollection('landing').path('/blog').first({ signal }))
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
-const { data: posts } = await useAsyncData('blog-posts', () => queryCollection('posts')
+const { data: posts } = await useAsyncData('blog-posts', (_nuxtApp, { signal }) => queryCollection('posts')
.where('path', 'LIKE', '/blog%')
.where('draft', '=', 0)
.order('date', 'DESC')
- .all(),
+ .all({ signal }),
)
useSeoMeta({
diff --git a/docs/app/pages/changelog/index.vue b/docs/app/pages/changelog/index.vue
index 97a52d561..20ce8f786 100644
--- a/docs/app/pages/changelog/index.vue
+++ b/docs/app/pages/changelog/index.vue
@@ -11,16 +11,16 @@ const { isScrolling, arrivedState } = useScroll(document)
const siteConfig = useSiteConfig()
-const { data: page } = await useAsyncData('changelog-landing', () => queryCollection('landing').path('/changelog').first())
+const { data: page } = await useAsyncData('changelog-landing', (_nuxtApp, { signal }) => queryCollection('landing').path('/changelog').first({ signal }))
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
-const { data: posts } = await useAsyncData('changelog-posts', () => queryCollection('posts')
+const { data: posts } = await useAsyncData('changelog-posts', (_nuxtApp, { signal }) => queryCollection('posts')
.where('path', 'LIKE', '/changelog%')
.where('draft', '=', 0)
.order('date', 'DESC')
- .all(),
+ .all({ signal }),
)
useSeoMeta({
diff --git a/docs/app/pages/studio/index.vue b/docs/app/pages/studio/index.vue
index dd5ec2fd2..94a72b2e8 100644
--- a/docs/app/pages/studio/index.vue
+++ b/docs/app/pages/studio/index.vue
@@ -1,7 +1,7 @@
diff --git a/examples/i18n/app/pages/[...slug].vue b/examples/i18n/app/pages/[...slug].vue
index b20922e42..6dd5b4c93 100644
--- a/examples/i18n/app/pages/[...slug].vue
+++ b/examples/i18n/app/pages/[...slug].vue
@@ -6,9 +6,9 @@ const route = useRoute()
const { locale, localeProperties } = useI18n()
const slug = computed(() => withLeadingSlash(String(route.params.slug)))
-const { data: page } = await useAsyncData('page-' + slug.value, async () => {
+const { data: page } = await useAsyncData('page-' + slug.value, async (_nuxtApp, { signal }) => {
const collection = ('content_' + locale.value) as keyof Collections
- const content = await queryCollection(collection).path(slug.value).first()
+ const content = await queryCollection(collection).path(slug.value).first({ signal })
// Possibly fallback to default locale if content is missing in non-default locale
diff --git a/examples/ui/app/pages/[...slug].vue b/examples/ui/app/pages/[...slug].vue
index b9e3943f3..6503256af 100644
--- a/examples/ui/app/pages/[...slug].vue
+++ b/examples/ui/app/pages/[...slug].vue
@@ -1,8 +1,8 @@
diff --git a/playground/pages/content-v2/[...slug].vue b/playground/pages/content-v2/[...slug].vue
index a2292ee1c..261b39c2c 100644
--- a/playground/pages/content-v2/[...slug].vue
+++ b/playground/pages/content-v2/[...slug].vue
@@ -1,16 +1,17 @@
diff --git a/playground/pages/hackernews/[...slug].vue b/playground/pages/hackernews/[...slug].vue
index f35177a18..806102f67 100644
--- a/playground/pages/hackernews/[...slug].vue
+++ b/playground/pages/hackernews/[...slug].vue
@@ -1,12 +1,12 @@
diff --git a/playground/pages/nuxt/[...slug].vue b/playground/pages/nuxt/[...slug].vue
index b40356f67..35cec2f1e 100644
--- a/playground/pages/nuxt/[...slug].vue
+++ b/playground/pages/nuxt/[...slug].vue
@@ -1,16 +1,17 @@
diff --git a/playground/pages/vue/[...slug].vue b/playground/pages/vue/[...slug].vue
index a5d2dd554..9bdccaa01 100644
--- a/playground/pages/vue/[...slug].vue
+++ b/playground/pages/vue/[...slug].vue
@@ -1,16 +1,17 @@
diff --git a/src/runtime/client.ts b/src/runtime/client.ts
index 7b8915a49..1b820a9d5 100644
--- a/src/runtime/client.ts
+++ b/src/runtime/client.ts
@@ -16,36 +16,55 @@ interface ChainablePromise extends Promise(collection: T): CollectionQueryBuilder => {
const event = tryUseNuxtApp()?.ssrContext?.event
- return collectionQueryBuilder(collection, (collection, sql) => executeContentQuery(event, collection, sql))
+ return collectionQueryBuilder(collection, (collection, sql, opts) => executeContentQuery(event, collection, sql, { signal: opts?.signal }))
}
-export function queryCollectionNavigation(collection: T, fields?: Array): ChainablePromise {
- return chainablePromise(collection, qb => generateNavigationTree(qb, fields))
+export interface QueryCollectionNavigationOptions {
+ signal?: AbortSignal
}
-export function queryCollectionItemSurroundings(collection: T, path: string, opts?: SurroundOptions): ChainablePromise {
+export function queryCollectionNavigation(collection: T, fields?: Array, { signal }: QueryCollectionNavigationOptions = {}): ChainablePromise {
+ return chainablePromise(collection, qb => generateNavigationTree(qb, fields, { signal }))
+}
+
+export interface QueryCollectionItemSurroundingsOptions extends SurroundOptions {
+ signal?: AbortSignal
+}
+
+export function queryCollectionItemSurroundings(collection: T, path: string, opts?: QueryCollectionItemSurroundingsOptions): ChainablePromise {
return chainablePromise(collection, qb => generateItemSurround(qb, path, opts))
}
-export function queryCollectionSearchSections(collection: keyof Collections, opts?: { ignoredTags: string[] }) {
+export interface QueryCollectionSearchSectionsOptions {
+ ignoredTags?: string[]
+ signal?: AbortSignal
+}
+
+export function queryCollectionSearchSections(collection: keyof Collections, opts?: QueryCollectionSearchSectionsOptions) {
return chainablePromise(collection, qb => generateSearchSections(qb, opts))
}
-async function executeContentQuery(event: H3Event | undefined, collection: T, sql: string) {
+async function executeContentQuery(event: H3Event | undefined, collection: T, sql: string, { signal }: { signal?: AbortSignal } = {}): Promise {
if (import.meta.client && window.WebAssembly) {
- return queryContentSqlClientWasm(collection, sql) as Promise
+ return queryContentSqlClientWasm(collection, sql, { signal }) as Promise
}
else {
- return fetchQuery(event, String(collection), sql) as Promise
+ return fetchQuery(event, String(collection), sql, { signal })
}
}
-async function queryContentSqlClientWasm(collection: T, sql: string) {
- const rows = await import('./internal/database.client')
- .then(m => m.loadDatabaseAdapter(collection))
- .then(db => db.all(sql))
-
- return rows as Result[]
+async function queryContentSqlClientWasm(collection: T, sql: string, { signal }: { signal?: AbortSignal } = {}): Promise {
+ return new Promise((resolve, reject) => {
+ // todo: explore aborting wasm queries with signal
+ signal?.addEventListener('abort', () => {
+ reject(new DOMException('Aborted', 'AbortError'))
+ })
+ import('./internal/database.client')
+ .then(m => m.loadDatabaseAdapter(collection))
+ .then(db => db.all(sql))
+ .then(resolve)
+ .catch(reject)
+ })
}
function chainablePromise(collection: T, fn: (qb: CollectionQueryBuilder) => Promise) {
diff --git a/src/runtime/internal/api.ts b/src/runtime/internal/api.ts
index 05a6c671a..6c09ff0b2 100644
--- a/src/runtime/internal/api.ts
+++ b/src/runtime/internal/api.ts
@@ -13,7 +13,11 @@ export async function fetchDatabase(event: H3Event | undefined, collection: stri
})
}
-export async function fetchQuery- (event: H3Event | undefined, collection: string, sql: string): Promise
- {
+export interface FetchQueryOptions {
+ signal?: AbortSignal
+}
+
+export async function fetchQuery
- (event: H3Event | undefined, collection: string, sql: string, { signal }: FetchQueryOptions = {}): Promise
- {
return await $fetch(`/__nuxt_content/${collection}/query`, {
context: event ? { cloudflare: event.context.cloudflare } : {},
headers: {
@@ -25,5 +29,6 @@ export async function fetchQuery
- (event: H3Event | undefined, collection: s
body: {
sql,
},
+ signal,
})
}
diff --git a/src/runtime/internal/navigation.ts b/src/runtime/internal/navigation.ts
index b7f8d8931..f1a20b164 100644
--- a/src/runtime/internal/navigation.ts
+++ b/src/runtime/internal/navigation.ts
@@ -5,22 +5,22 @@ import type { ContentNavigationItem, PageCollectionItemBase, CollectionQueryBuil
/**
* Create NavItem array to be consumed from runtime plugin.
*/
-export async function generateNavigationTree(queryBuilder: CollectionQueryBuilder, extraFields: Array = []) {
+export async function generateNavigationTree(queryBuilder: CollectionQueryBuilder, extraFields: Array = [], { signal }: { signal?: AbortSignal } = {}): Promise {
// @ts-expect-error -- internal
const params = queryBuilder.__params
if (!params?.orderBy?.length) {
queryBuilder = queryBuilder.order('stem', 'ASC')
}
- const collecitonItems = await queryBuilder
+ const collectionItems = await queryBuilder
.orWhere(group => group
.where('navigation', '<>', 'false')
.where('navigation', 'IS NULL'),
)
.select('navigation', 'stem', 'path', 'title', 'meta', ...(extraFields || []))
- .all() as unknown as PageCollectionItemBase[]
+ .all({ signal }) as unknown as PageCollectionItemBase[]
- const { contents, configs } = collecitonItems.reduce((acc, c) => {
+ const { contents, configs } = collectionItems.reduce((acc, c) => {
if (String(c.stem).split('/').pop() === '.navigation') {
c.title = c.title?.toLowerCase() === 'navigation' ? '' : c.title
const key = c.path!.split('/').slice(0, -1).join('/') || '/'
diff --git a/src/runtime/internal/query.ts b/src/runtime/internal/query.ts
index 6b617d225..ecec2b383 100644
--- a/src/runtime/internal/query.ts
+++ b/src/runtime/internal/query.ts
@@ -69,7 +69,7 @@ export const collectionQueryGroup = (collection: T)
return query
}
-export const collectionQueryBuilder = (collection: T, fetch: (collection: T, sql: string) => Promise): CollectionQueryBuilder => {
+export const collectionQueryBuilder = (collection: T, fetch: (collection: T, sql: string, opts: { signal?: AbortSignal }) => Promise): CollectionQueryBuilder => {
const params = {
conditions: [] as Array,
selectedFields: [] as Array,
@@ -121,16 +121,16 @@ export const collectionQueryBuilder = (collection:
params.orderBy.push(`"${String(field)}" ${direction}`)
return query
},
- async all(): Promise {
- return fetch(collection, buildQuery()).then(res => (res || []) as Collections[T][])
+ async all({ signal }: { signal?: AbortSignal } = {}): Promise {
+ return fetch(collection, buildQuery(), { signal }).then(res => (res || []) as Collections[T][])
},
- async first(): Promise {
- return fetch(collection, buildQuery({ limit: 1 })).then(res => res[0] || null)
+ async first({ signal }: { signal?: AbortSignal } = {}): Promise {
+ return fetch(collection, buildQuery({ limit: 1 }), { signal }).then(res => res[0] || null)
},
- async count(field: keyof Collections[T] | '*' = '*', distinct: boolean = false) {
+ async count(field: keyof Collections[T] | '*' = '*', distinct: boolean = false, { signal }: { signal?: AbortSignal } = {}) {
return fetch(collection, buildQuery({
count: { field: String(field), distinct },
- })).then(m => (m[0] as { count: number }).count)
+ }), { signal }).then(m => (m[0] as { count: number }).count)
},
}
diff --git a/src/runtime/internal/search.ts b/src/runtime/internal/search.ts
index d55cea5ca..93ca8fb3e 100644
--- a/src/runtime/internal/search.ts
+++ b/src/runtime/internal/search.ts
@@ -27,13 +27,19 @@ interface SectionablePage {
body: MDCRoot | MinimarkTree
}
-export async function generateSearchSections(queryBuilder: CollectionQueryBuilder, opts?: { ignoredTags?: string[], extraFields?: Array }) {
- const { ignoredTags = [], extraFields = [] } = opts || {}
+export interface GenerateSearchSectionsOptions {
+ ignoredTags?: string[]
+ extraFields?: Array
+ signal?: AbortSignal
+}
+
+export async function generateSearchSections(queryBuilder: CollectionQueryBuilder, opts?: GenerateSearchSectionsOptions) {
+ const { ignoredTags = [], extraFields = [], signal } = opts || {}
const documents = await queryBuilder
.where('extension', '=', 'md')
.select('path', 'body', 'description', 'title', ...(extraFields || []))
- .all()
+ .all({ signal })
return documents.flatMap(doc => splitPageIntoSections(doc, { ignoredTags, extraFields: extraFields as string[] }))
}
diff --git a/src/runtime/internal/surround.ts b/src/runtime/internal/surround.ts
index 3149b334f..b0234be73 100644
--- a/src/runtime/internal/surround.ts
+++ b/src/runtime/internal/surround.ts
@@ -2,9 +2,13 @@ import { generateNavigationTree } from './navigation'
import type { ContentNavigationItem, PageCollectionItemBase, SurroundOptions } from '@nuxt/content'
import type { CollectionQueryBuilder } from '~/src/types'
-export async function generateItemSurround(queryBuilder: CollectionQueryBuilder, path: string, opts?: SurroundOptions) {
+export interface GenerateItemSurroundOptions extends SurroundOptions {
+ signal?: AbortSignal
+}
+
+export async function generateItemSurround(queryBuilder: CollectionQueryBuilder, path: string, opts?: GenerateItemSurroundOptions) {
const { before = 1, after = 1, fields = [] } = opts || {}
- const navigation = await generateNavigationTree(queryBuilder, fields)
+ const navigation = await generateNavigationTree(queryBuilder, fields, { signal: opts?.signal })
const flatData = flattedData(navigation)
const index = flatData.findIndex(item => item.path === path)
diff --git a/src/types/query.ts b/src/types/query.ts
index 5b013feaa..edc0faff8 100644
--- a/src/types/query.ts
+++ b/src/types/query.ts
@@ -8,9 +8,9 @@ export interface CollectionQueryBuilder {
order(field: keyof T, direction: 'ASC' | 'DESC'): CollectionQueryBuilder
skip(skip: number): CollectionQueryBuilder
limit(limit: number): CollectionQueryBuilder
- all(): Promise
- first(): Promise
- count(field?: keyof T | '*', distinct?: boolean): Promise
+ all(opts?: { signal?: AbortSignal }): Promise
+ first(opts?: { signal?: AbortSignal }): Promise
+ count(field?: keyof T | '*', distinct?: boolean, opts?: { signal?: AbortSignal }): Promise
where(field: string, operator: SQLOperator, value?: unknown): CollectionQueryBuilder
andWhere(groupFactory: QueryGroupFunction): CollectionQueryBuilder
orWhere(groupFactory: QueryGroupFunction): CollectionQueryBuilder
diff --git a/test/unit/collectionQueryBuilder.test.ts b/test/unit/collectionQueryBuilder.test.ts
index d6630439b..206f8b917 100644
--- a/test/unit/collectionQueryBuilder.test.ts
+++ b/test/unit/collectionQueryBuilder.test.ts
@@ -12,7 +12,7 @@ vi.mock('#content/manifest', () => ({
const mockFetch = vi.fn().mockResolvedValue(Promise.resolve([{}]))
const mockCollection = 'articles' as never
-describe('collectionQueryBuilder', () => {
+describe.only('collectionQueryBuilder', () => {
beforeEach(() => {
mockFetch.mockClear()
})
@@ -21,17 +21,34 @@ describe('collectionQueryBuilder', () => {
const query = collectionQueryBuilder(mockCollection, mockFetch)
await query.all()
- expect(mockFetch).toHaveBeenCalledWith('articles', 'SELECT * FROM _articles ORDER BY stem ASC')
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with where clause', async () => {
const query = collectionQueryBuilder(mockCollection, mockFetch)
await query.where('title', '=', 'Test Article').all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles WHERE ("title" = \'Test Article\') ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("title" = 'Test Article') ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with multiple where clauses', async () => {
@@ -41,10 +58,17 @@ describe('collectionQueryBuilder', () => {
.where('published', '=', true)
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles WHERE ("title" = \'Test Article\') AND ("published" = \'1\') ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("title" = 'Test Article') AND ("published" = '1') ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with IN operator', async () => {
@@ -53,10 +77,17 @@ describe('collectionQueryBuilder', () => {
.where('category', 'IN', ['news', 'tech'])
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles WHERE ("category" IN (\'news\', \'tech\')) ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("category" IN ('news', 'tech')) ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with BETWEEN operator', async () => {
@@ -65,10 +96,17 @@ describe('collectionQueryBuilder', () => {
.where('date', 'BETWEEN', ['2023-01-01', '2023-12-31'])
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles WHERE ("date" BETWEEN \'2023-01-01\' AND \'2023-12-31\') ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("date" BETWEEN '2023-01-01' AND '2023-12-31') ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with selected fields', async () => {
@@ -77,10 +115,17 @@ describe('collectionQueryBuilder', () => {
.select('title', 'date', 'author')
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT "title", "date", "author" FROM _articles ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT "title", "date", "author" FROM _articles ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with order by', async () => {
@@ -89,10 +134,17 @@ describe('collectionQueryBuilder', () => {
.order('date', 'DESC')
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles ORDER BY "date" DESC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles ORDER BY "date" DESC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with limit without skip', async () => {
@@ -101,10 +153,17 @@ describe('collectionQueryBuilder', () => {
.limit(5)
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles ORDER BY stem ASC LIMIT 5',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles ORDER BY stem ASC LIMIT 5",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with limit and offset', async () => {
@@ -114,40 +173,68 @@ describe('collectionQueryBuilder', () => {
.skip(20)
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles ORDER BY stem ASC LIMIT 10 OFFSET 20',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles ORDER BY stem ASC LIMIT 10 OFFSET 20",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with first()', async () => {
const query = collectionQueryBuilder(mockCollection, mockFetch)
await query.first()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles ORDER BY stem ASC LIMIT 1',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles ORDER BY stem ASC LIMIT 1",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds count query', async () => {
const query = collectionQueryBuilder(mockCollection, mockFetch)
await query.count()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT COUNT(*) as count FROM _articles ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT COUNT(*) as count FROM _articles ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds distinct count query', async () => {
const query = collectionQueryBuilder(mockCollection, mockFetch)
await query.count('author', true)
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT COUNT(DISTINCT author) as count FROM _articles ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT COUNT(DISTINCT author) as count FROM _articles ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with complex where conditions using andWhere', async () => {
@@ -163,10 +250,17 @@ describe('collectionQueryBuilder', () => {
)
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles WHERE ("published" = \'1\') AND ("category" = \'tech\' AND ("tags" LIKE \'%javascript%\' OR "tags" LIKE \'%typescript%\')) ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("published" = '1') AND ("category" = 'tech' AND ("tags" LIKE '%javascript%' OR "tags" LIKE '%typescript%')) ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
})
it('builds query with path', async () => {
@@ -175,9 +269,44 @@ describe('collectionQueryBuilder', () => {
.path('/blog/my-article')
.all()
- expect(mockFetch).toHaveBeenCalledWith(
- 'articles',
- 'SELECT * FROM _articles WHERE ("path" = \'/blog/my-article\') ORDER BY stem ASC',
- )
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("path" = '/blog/my-article') ORDER BY stem ASC",
+ {
+ "signal": undefined,
+ },
+ ],
+ ]
+ `)
+ })
+
+ it.only('builds query with signal', async () => {
+ const query = collectionQueryBuilder(mockCollection, mockFetch)
+ const abortController = new AbortController()
+ await query
+ .where('title', '=', 'Test Article')
+ .all({ signal: abortController.signal })
+
+ expect(mockFetch.mock.calls).toMatchInlineSnapshot(`
+ [
+ [
+ "articles",
+ "SELECT * FROM _articles WHERE ("title" = 'Test Article') ORDER BY stem ASC",
+ {
+ "signal": AbortSignal {
+ Symbol(kEvents): Map {},
+ Symbol(events.maxEventTargetListeners): 0,
+ Symbol(events.maxEventTargetListenersWarned): false,
+ Symbol(kHandlers): Map {},
+ Symbol(kAborted): false,
+ Symbol(kReason): undefined,
+ Symbol(kComposite): false,
+ },
+ },
+ ],
+ ]
+ `)
})
})