mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
feat(content api): add client library api reference search results (#35484)
* feat(content api): add client library api reference search results Allow searchDocs results to also return function references from the client library APIs * fix(content api): refine language enum handling
This commit is contained in:
1 parent
22ee5dfe2a
commit
badcf17f70
11 files changed
+249
-29
No files matched your search
@@ -70,6 +70,35 @@ type Subsection {
|
||||
content: String
|
||||
}
|
||||
|
||||
"""
|
||||
A reference document containing a description of a function from a Supabase client library
|
||||
"""
|
||||
type ClientLibraryFunctionReference implements SearchResult {
|
||||
"""The title of the document"""
|
||||
title: String
|
||||
|
||||
"""The URL of the document"""
|
||||
href: String
|
||||
|
||||
"""The content of the reference document, as text"""
|
||||
content: String
|
||||
|
||||
"""The programming language for which the function is written"""
|
||||
language: Language!
|
||||
|
||||
"""The name of the function or method"""
|
||||
methodName: String
|
||||
}
|
||||
|
||||
enum Language {
|
||||
JAVASCRIPT
|
||||
SWIFT
|
||||
DART
|
||||
CSHARP
|
||||
KOTLIN
|
||||
PYTHON
|
||||
}
|
||||
|
||||
type RootQueryType {
|
||||
"""Get the GraphQL schema for this endpoint"""
|
||||
schema: String!
|
||||
|
||||
@@ -15,8 +15,11 @@ type Database = {
|
||||
Returns: Array<
|
||||
Omit<
|
||||
DatabaseGenerated['public']['Functions']['search_content']['Returns'][number],
|
||||
'subsections'
|
||||
> & { subsections: Array<{ title?: string; href?: string; content?: string }> }
|
||||
'subsections' | 'metadata'
|
||||
> & {
|
||||
metadata: { language?: string; methodName?: string }
|
||||
subsections: Array<{ title?: string; href?: string; content?: string }>
|
||||
}
|
||||
>
|
||||
}
|
||||
}
|
||||
@@ -38,3 +41,5 @@ export function supabase() {
|
||||
|
||||
return _supabase
|
||||
}
|
||||
|
||||
export type { Database as DatabaseCorrected }
|
||||
@@ -2,8 +2,10 @@ import { type RootQueryTypeSearchDocsArgs } from '~/__generated__/graphql'
|
||||
import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils'
|
||||
import { Result } from '~/features/helpers.fn'
|
||||
import { openAI } from '~/lib/openAi'
|
||||
import { supabase } from '~/lib/supabase'
|
||||
import { supabase, type DatabaseCorrected } from '~/lib/supabase'
|
||||
import { GuideModel } from '../guide/guideModel'
|
||||
import { ReferenceSDKFunctionModel, SDKLanguageValues } from '../reference/referenceSDKModel'
|
||||
import { SearchResultInterface } from './globalSearchInterface'
|
||||
|
||||
export abstract class SearchResultModel {
|
||||
static async search(
|
||||
@@ -22,26 +24,42 @@ export abstract class SearchResultModel {
|
||||
max_result: args.limit,
|
||||
})
|
||||
)
|
||||
.map((matches) =>
|
||||
matches
|
||||
.map(({ type, page_title, href, content, subsections }) => {
|
||||
switch (type) {
|
||||
case 'markdown':
|
||||
return new GuideModel({
|
||||
title: page_title,
|
||||
href,
|
||||
content,
|
||||
subsections,
|
||||
})
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
)
|
||||
.map((matches) => matches.map(createModelFromMatch).filter(Boolean))
|
||||
.mapError(convertPostgrestToApiError)
|
||||
|
||||
return matchResult
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function createModelFromMatch({
|
||||
type,
|
||||
page_title,
|
||||
href,
|
||||
content,
|
||||
metadata,
|
||||
subsections,
|
||||
}: DatabaseCorrected['public']['Functions']['search_content']['Returns'][number]): SearchResultInterface | null {
|
||||
switch (type) {
|
||||
case 'markdown':
|
||||
return new GuideModel({
|
||||
title: page_title,
|
||||
href,
|
||||
content,
|
||||
subsections,
|
||||
})
|
||||
case 'reference':
|
||||
const { language } = metadata
|
||||
if (SDKLanguageValues.includes(language)) {
|
||||
return new ReferenceSDKFunctionModel({
|
||||
title: page_title,
|
||||
href,
|
||||
content,
|
||||
language,
|
||||
methodName: metadata.methodName,
|
||||
})
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SearchResultInterface } from '../globalSearch/globalSearchInterface'
|
||||
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
|
||||
|
||||
export class GuideModel implements SearchResultInterface {
|
||||
public title?: string
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { type SearchResultInterface } from '../globalSearch/globalSearchInterface'
|
||||
|
||||
export const SDKLanguages: Record<string, { value: string; pathSection: string }> = {
|
||||
JAVASCRIPT: {
|
||||
value: 'JavaScript',
|
||||
pathSection: 'javascript',
|
||||
},
|
||||
SWIFT: {
|
||||
value: 'Swift',
|
||||
pathSection: 'swift',
|
||||
},
|
||||
DART: {
|
||||
value: 'Dart',
|
||||
pathSection: 'dart',
|
||||
},
|
||||
CSHARP: {
|
||||
value: 'C#',
|
||||
pathSection: 'csharp',
|
||||
},
|
||||
KOTLIN: {
|
||||
value: 'Kotlin',
|
||||
pathSection: 'kotlin',
|
||||
},
|
||||
PYTHON: {
|
||||
value: 'Python',
|
||||
pathSection: 'python',
|
||||
},
|
||||
}
|
||||
|
||||
export const SDKLanguageValues = Object.values(SDKLanguages).map(({ value }) => value)
|
||||
|
||||
export class ReferenceSDKFunctionModel implements SearchResultInterface {
|
||||
public title?: string
|
||||
public href?: string
|
||||
public content?: string
|
||||
public language: string
|
||||
public methodName?: string
|
||||
|
||||
constructor({
|
||||
title,
|
||||
href,
|
||||
content,
|
||||
language,
|
||||
methodName,
|
||||
}: {
|
||||
title?: string
|
||||
href?: string
|
||||
content?: string
|
||||
language: string
|
||||
methodName?: string
|
||||
}) {
|
||||
this.title = title
|
||||
this.href = href
|
||||
this.content = content
|
||||
this.methodName = methodName
|
||||
|
||||
if (SDKLanguageValues.includes(language)) {
|
||||
this.language = language
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { GraphQLEnumType, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql'
|
||||
import { GraphQLInterfaceTypeSearchResult } from '../globalSearch/globalSearchSchema'
|
||||
import { ReferenceSDKFunctionModel, SDKLanguages } from './referenceSDKModel'
|
||||
|
||||
const GraphQLEnumLanguage = new GraphQLEnumType({
|
||||
name: 'Language',
|
||||
values: Object.keys(SDKLanguages).reduce((acc, key) => {
|
||||
acc[key] = { value: SDKLanguages[key].value }
|
||||
return acc
|
||||
}, {}),
|
||||
})
|
||||
|
||||
export const GraphQLObjectTypeReferenceSDKFunction = new GraphQLObjectType({
|
||||
name: 'ClientLibraryFunctionReference',
|
||||
interfaces: [GraphQLInterfaceTypeSearchResult],
|
||||
isTypeOf: (value: unknown) => value instanceof ReferenceSDKFunctionModel,
|
||||
description:
|
||||
'A reference document containing a description of a function from a Supabase client library',
|
||||
fields: {
|
||||
title: {
|
||||
type: GraphQLString,
|
||||
description: 'The title of the document',
|
||||
},
|
||||
href: {
|
||||
type: GraphQLString,
|
||||
description: 'The URL of the document',
|
||||
},
|
||||
content: {
|
||||
type: GraphQLString,
|
||||
description: 'The content of the reference document, as text',
|
||||
},
|
||||
language: {
|
||||
type: new GraphQLNonNull(GraphQLEnumLanguage),
|
||||
description: 'The programming language for which the function is written',
|
||||
},
|
||||
methodName: {
|
||||
type: GraphQLString,
|
||||
description: 'The name of the function or method',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { RootQueryTypeResolvers } from '~/__generated__/graphql'
|
||||
import { searchRoot } from './globalSearch/globalSearchResolver'
|
||||
import { GraphQLObjectTypeGuide } from './guide/guideSchema'
|
||||
import { GraphQLObjectTypeReferenceSDKFunction } from './reference/referenceSDKSchema'
|
||||
|
||||
const GRAPHQL_FIELD_INTROSPECT = 'schema' as const
|
||||
|
||||
@@ -34,5 +35,5 @@ export const rootGraphQLSchema = new GraphQLSchema({
|
||||
...searchRoot,
|
||||
},
|
||||
}),
|
||||
types: [GraphQLObjectTypeGuide],
|
||||
types: [GraphQLObjectTypeGuide, GraphQLObjectTypeReferenceSDKFunction],
|
||||
})
|
||||
@@ -56,7 +56,7 @@ export async function fetchJsLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'js-lib',
|
||||
'/reference/javascript',
|
||||
{ title: 'JavaScript Reference' },
|
||||
{ title: 'JavaScript Reference', language: 'JavaScript' },
|
||||
'spec/supabase_js_v2.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
@@ -66,7 +66,7 @@ export async function fetchDartLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'dart-lib',
|
||||
'/reference/dart',
|
||||
{ title: 'Dart Reference' },
|
||||
{ title: 'Dart Reference', language: 'Dart' },
|
||||
'spec/supabase_dart_v2.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
@@ -76,7 +76,7 @@ export async function fetchPythonLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'python-lib',
|
||||
'/reference/python',
|
||||
{ title: 'Python Reference' },
|
||||
{ title: 'Python Reference', language: 'Python' },
|
||||
'spec/supabase_py_v2.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
@@ -86,7 +86,7 @@ export async function fetchCSharpLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'csharp-lib',
|
||||
'/reference/csharp',
|
||||
{ title: 'C# Reference' },
|
||||
{ title: 'C# Reference', language: 'C#' },
|
||||
'spec/supabase_csharp_v0.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
@@ -96,7 +96,7 @@ export async function fetchSwiftLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'swift-lib',
|
||||
'/reference/swift',
|
||||
{ title: 'Swift Reference' },
|
||||
{ title: 'Swift Reference', language: 'Swift' },
|
||||
'spec/supabase_swift_v2.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
@@ -106,7 +106,7 @@ export async function fetchKtLibReferenceSource() {
|
||||
return new ClientLibReferenceLoader(
|
||||
'kt-lib',
|
||||
'/reference/kotlin',
|
||||
{ title: 'Kotlin Reference' },
|
||||
{ title: 'Kotlin Reference', language: 'Kotlin' },
|
||||
'spec/supabase_kt_v1.yml',
|
||||
'spec/common-client-libs-sections.json'
|
||||
).load()
|
||||
|
||||
@@ -52,7 +52,7 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
|
||||
`${this.path}/${refSection.slug}`,
|
||||
refSection,
|
||||
specSection,
|
||||
this.meta
|
||||
this.enhanceMeta(specSection)
|
||||
)
|
||||
})
|
||||
.filter(Boolean)
|
||||
@@ -62,6 +62,9 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
|
||||
|
||||
abstract getSpecSections(specContents: string): SpecSection[]
|
||||
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection
|
||||
enhanceMeta(section: SpecSection): Json {
|
||||
return this.meta
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ReferenceSource<SpecSection> extends BaseSource {
|
||||
@@ -186,9 +189,14 @@ export class ClientLibReferenceLoader extends ReferenceLoader<IFunctionDefinitio
|
||||
|
||||
return spec.functions
|
||||
}
|
||||
|
||||
matchSpecSection(functionDefinitions: IFunctionDefinition[], id: string): IFunctionDefinition {
|
||||
return functionDefinitions.find((functionDefinition) => functionDefinition.id === id)
|
||||
}
|
||||
|
||||
enhanceMeta(section: IFunctionDefinition): Json {
|
||||
return { ...this.meta, slug: section.id, methodName: section.title }
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientLibReferenceSource extends ReferenceSource<IFunctionDefinition> {
|
||||
|
||||
@@ -552,6 +552,7 @@ export type Database = {
|
||||
type: string
|
||||
href: string
|
||||
content: string
|
||||
metadata: Json
|
||||
subsections: Json[]
|
||||
}[]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
-- Alter the search_content function to also return the page metadata
|
||||
|
||||
drop function search_content;
|
||||
|
||||
create or replace function search_content(
|
||||
embedding vector(1536),
|
||||
include_full_content boolean default false,
|
||||
match_threshold float default 0.78,
|
||||
max_result int default 30
|
||||
)
|
||||
returns table (
|
||||
id bigint,
|
||||
page_title text,
|
||||
type text,
|
||||
href text,
|
||||
content text,
|
||||
metadata json,
|
||||
subsections json[]
|
||||
)
|
||||
language sql
|
||||
set search_path = ''
|
||||
as $$
|
||||
with matched_section as (
|
||||
select
|
||||
*,
|
||||
row_number() over () as ranking
|
||||
from public.match_embedding(
|
||||
embedding,
|
||||
match_threshold,
|
||||
max_result
|
||||
)
|
||||
)
|
||||
select
|
||||
page.id,
|
||||
meta ->> 'title' as page_title,
|
||||
type,
|
||||
public.get_full_content_url(type, path, null) as href,
|
||||
case
|
||||
when include_full_content
|
||||
then page.content
|
||||
else
|
||||
null
|
||||
end as content,
|
||||
meta as metadata,
|
||||
array_agg(
|
||||
json_build_object(
|
||||
'title', heading,
|
||||
'href', public.get_full_content_url(type, path, slug),
|
||||
'content', matched_section.content
|
||||
)
|
||||
)
|
||||
from matched_section
|
||||
join public.page on matched_section.page_id = page.id
|
||||
group by page.id
|
||||
order by min(ranking);
|
||||
$$;
|
||||
Reference in new issue
Block a user