Implement basic searching

This commit is contained in:
Vladyslav Fedoriuk 2023-09-17 14:23:04 +02:00
parent 1441cbb396
commit 9d286f5c01
16 changed files with 565 additions and 22 deletions

View File

@ -9,6 +9,9 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.3.1",
"@orama/orama": "^1.2.3",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-table": "^8.9.3", "@tanstack/react-table": "^8.9.3",
"@types/node": "20.5.1", "@types/node": "20.5.1",
@ -24,10 +27,11 @@
"postcss": "8.4.28", "postcss": "8.4.28",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.46.1",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3", "tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6", "tailwindcss-animate": "^1.0.6",
"typescript": "5.1.6", "typescript": "5.1.6",
"zod": "^3.22.2" "zod": "^3.21.4"
} }
} }

102
frontend/pnpm-lock.yaml generated
View File

@ -5,6 +5,15 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: dependencies:
"@hookform/resolvers":
specifier: ^3.3.1
version: 3.3.1(react-hook-form@7.46.1)
"@orama/orama":
specifier: ^1.2.3
version: 1.2.3
"@radix-ui/react-label":
specifier: ^2.0.2
version: 2.0.2(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
"@radix-ui/react-slot": "@radix-ui/react-slot":
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.2(@types/react@18.2.20)(react@18.2.0) version: 1.0.2(@types/react@18.2.20)(react@18.2.0)
@ -50,6 +59,9 @@ dependencies:
react-dom: react-dom:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0(react@18.2.0) version: 18.2.0(react@18.2.0)
react-hook-form:
specifier: ^7.46.1
version: 7.46.1(react@18.2.0)
tailwind-merge: tailwind-merge:
specifier: ^1.14.0 specifier: ^1.14.0
version: 1.14.0 version: 1.14.0
@ -63,8 +75,8 @@ dependencies:
specifier: 5.1.6 specifier: 5.1.6
version: 5.1.6 version: 5.1.6
zod: zod:
specifier: ^3.22.2 specifier: ^3.21.4
version: 3.22.2 version: 3.21.4
packages: packages:
/@aashutoshrathi/word-wrap@1.2.6: /@aashutoshrathi/word-wrap@1.2.6:
@ -142,6 +154,17 @@ packages:
engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 }
dev: false dev: false
/@hookform/resolvers@3.3.1(react-hook-form@7.46.1):
resolution:
{
integrity: sha512-K7KCKRKjymxIB90nHDQ7b9nli474ru99ZbqxiqDAWYsYhOsU3/4qLxW91y+1n04ic13ajjZ66L3aXbNef8PELQ==,
}
peerDependencies:
react-hook-form: ^7.0.0
dependencies:
react-hook-form: 7.46.1(react@18.2.0)
dev: false
/@humanwhocodes/config-array@0.11.10: /@humanwhocodes/config-array@0.11.10:
resolution: resolution:
{ {
@ -370,6 +393,14 @@ packages:
fastq: 1.15.0 fastq: 1.15.0
dev: false dev: false
/@orama/orama@1.2.3:
resolution:
{
integrity: sha512-KJ4lzTDluQOJu6l2xsmDjKdhU6EvldmshvsQgAvDORn/Db+EXaWOKSK4XdvUNIcpUeSbFAdkRB26NLlfSpWRGg==,
}
engines: { node: ">= 16.0.0" }
dev: false
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.20)(react@18.2.0): /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.20)(react@18.2.0):
resolution: resolution:
{ {
@ -387,6 +418,54 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-label@2.0.2(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
resolution:
{
integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==,
}
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
dependencies:
"@babel/runtime": 7.22.10
"@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
"@types/react": 18.2.20
"@types/react-dom": 18.2.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
resolution:
{
integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==,
}
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
dependencies:
"@babel/runtime": 7.22.10
"@radix-ui/react-slot": 1.0.2(@types/react@18.2.20)(react@18.2.0)
"@types/react": 18.2.20
"@types/react-dom": 18.2.7
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-slot@1.0.2(@types/react@18.2.20)(react@18.2.0): /@radix-ui/react-slot@1.0.2(@types/react@18.2.20)(react@18.2.0):
resolution: resolution:
{ {
@ -2964,6 +3043,18 @@ packages:
scheduler: 0.23.0 scheduler: 0.23.0
dev: false dev: false
/react-hook-form@7.46.1(react@18.2.0):
resolution:
{
integrity: sha512-0GfI31LRTBd5tqbXMGXT1Rdsv3rnvy0FjEk8Gn9/4tp6+s77T7DPZuGEpBRXOauL+NhyGT5iaXzdIM2R6F/E+w==,
}
engines: { node: ">=12.22.0" }
peerDependencies:
react: ^16.8.0 || ^17 || ^18
dependencies:
react: 18.2.0
dev: false
/react-is@16.13.1: /react-is@16.13.1:
resolution: resolution:
{ {
@ -3707,10 +3798,3 @@ packages:
integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==, integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==,
} }
dev: false dev: false
/zod@3.22.2:
resolution:
{
integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==,
}
dev: false

View File

@ -31,8 +31,8 @@ export const columns: ColumnDef<Repo>[] = [
<path <path
d="M7.49933 0.25C3.49635 0.25 0.25 3.49593 0.25 7.50024C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2222 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3149 2.49584 10.3238 2.49584 10.3238C3.22353 10.375 3.60629 11.0711 3.60629 11.0711C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89918 2.87415 7.12104C2.87415 6.32925 3.15677 5.68257 3.62053 5.17563C3.54576 4.99226 3.29697 4.25521 3.69174 3.25691C3.69174 3.25691 4.30015 3.06196 5.68522 3.99973C6.26337 3.83906 6.8838 3.75895 7.50022 3.75583C8.1162 3.75895 8.73619 3.83906 9.31523 3.99973C10.6994 3.06196 11.3069 3.25691 11.3069 3.25691C11.7026 4.25521 11.4538 4.99226 11.3795 5.17563C11.8441 5.68257 12.1245 6.32925 12.1245 7.12104C12.1245 9.9063 10.4292 10.5192 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50024C14.75 3.49593 11.5036 0.25 7.49933 0.25Z" d="M7.49933 0.25C3.49635 0.25 0.25 3.49593 0.25 7.50024C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2222 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3149 2.49584 10.3238 2.49584 10.3238C3.22353 10.375 3.60629 11.0711 3.60629 11.0711C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89918 2.87415 7.12104C2.87415 6.32925 3.15677 5.68257 3.62053 5.17563C3.54576 4.99226 3.29697 4.25521 3.69174 3.25691C3.69174 3.25691 4.30015 3.06196 5.68522 3.99973C6.26337 3.83906 6.8838 3.75895 7.50022 3.75583C8.1162 3.75895 8.73619 3.83906 9.31523 3.99973C10.6994 3.06196 11.3069 3.25691 11.3069 3.25691C11.7026 4.25521 11.4538 4.99226 11.3795 5.17563C11.8441 5.68257 12.1245 6.32925 12.1245 7.12104C12.1245 9.9063 10.4292 10.5192 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50024C14.75 3.49593 11.5036 0.25 7.49933 0.25Z"
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="evenodd"
clip-rule="evenodd" clipRule="evenodd"
></path> ></path>
</svg> </svg>
<a <a

View File

@ -1,12 +1,15 @@
import { loadIndexServerOnly } from "@/lib/load-index-server-only"; import { loadIndexServerOnly } from "@/lib/repos-index";
import { DataTable } from "./data-table"; import { ReposTable } from "./repos-table";
import { columns } from "./columns"; import { ReposSearchProvider } from "./repos-search-provider";
export default async function Home() { export default async function Home() {
const { repos } = await loadIndexServerOnly(); const { repos } = await loadIndexServerOnly();
return ( return (
<section className="py-10"> <section className="py-10">
<DataTable columns={columns} data={repos} /> <ReposSearchProvider repos={repos}>
<ReposTable repos={repos} />
</ReposSearchProvider>
</section> </section>
); );
} }

View File

@ -0,0 +1,31 @@
"use client";
import {
createReposOrama,
prepareReposOramaIndex,
ReposOramaContext,
} from "@/lib/search";
import { PropsWithChildren } from "react";
import { SearchProvider } from "./search-provider";
import { Index } from "@/lib/schemas";
export function ReposSearchProvider({
children,
repos,
}: PropsWithChildren<{
repos: Index["repos"];
}>) {
const prepareOramaIndex = async () => {
const orama = await createReposOrama();
await prepareReposOramaIndex(orama, repos);
return orama;
};
return (
<SearchProvider
createIndex={prepareOramaIndex}
OramaContext={ReposOramaContext}
>
{children}
</SearchProvider>
);
}

View File

@ -0,0 +1,38 @@
"use client";
import { Index, Repo } from "@/lib/schemas";
import { search } from "@orama/orama";
import { SearchForm } from "./search-form";
import { columns } from "./columns";
import { DataTable } from "./data-table";
import { useReposOrama } from "@/lib/search";
import { useState } from "react";
export function ReposTable({ repos }: Index) {
const reposOrama = useReposOrama();
const [searchedRepos, setSearchedRepos] = useState<Repo[]>(repos);
const onSearchSubmit = async ({
search: description,
}: {
search: string;
}) => {
if (!reposOrama.isIndexed || !reposOrama.orama) {
throw new Error("Orama is not initialized");
}
const results = await search<Repo>(reposOrama.orama, {
term: description,
properties: ["description"],
limit: repos.length,
});
setSearchedRepos(results.hits.map((hit) => hit.document as Repo));
};
return (
<>
<div className="mb-4">
<SearchForm onSubmit={onSearchSubmit} />
</div>
<DataTable columns={columns} data={searchedRepos} />
</>
);
}

View File

@ -0,0 +1,56 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
const FormSchema = z.object({
search: z
.string()
.max(1024, { message: "Search must be less than 1024 characters" }),
});
export interface SearchFormProps {
onSubmit: (data: z.infer<typeof FormSchema>) => void;
}
export function SearchForm({ onSubmit }: SearchFormProps) {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
<FormField
control={form.control}
name="search"
render={({ field }) => (
<FormItem>
<FormLabel>Search for a repository</FormLabel>
<FormControl>
<Input placeholder="fastapi" {...field} />
</FormControl>
<FormDescription>
The search is performed on the repository description.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Search</Button>
</form>
</Form>
);
}

View File

@ -0,0 +1,38 @@
"use client";
import { IOramaContext } from "@/lib/search";
import { Context, PropsWithChildren, useEffect, useState } from "react";
import { Orama, ProvidedTypes as OramaProvidedTypes } from "@orama/orama";
export type SearchProviderProps<
OramaParameters extends Partial<OramaProvidedTypes> = any,
> = PropsWithChildren<{
OramaContext: Context<IOramaContext<OramaParameters>>;
createIndex: () => Promise<Orama<OramaParameters>>;
}>;
export function SearchProvider<
OramaParameters extends Partial<OramaProvidedTypes>,
>({
children,
OramaContext,
createIndex,
}: SearchProviderProps<OramaParameters>) {
const [orama, setOrama] = useState<Orama<OramaParameters> | null>(null);
const [isIndexed, setIsIndexed] = useState(false);
useEffect(() => {
async function initializeOrama() {
setIsIndexed(false);
await createIndex().then(setOrama);
setIsIndexed(true);
}
initializeOrama();
}, [createIndex]);
return (
<OramaContext.Provider value={{ orama, isIndexed }}>
{children}
</OramaContext.Provider>
);
}

View File

@ -0,0 +1,177 @@
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
type FormItemContextValue = {
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};

View File

@ -0,0 +1,25 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = "Input";
export { Input };

View File

@ -0,0 +1,26 @@
"use client";
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View File

@ -21,6 +21,12 @@ export const loadIndexServerOnly = cache(async () => {
try { try {
const indexData = JSON.parse( const indexData = JSON.parse(
await fs.promises.readFile(INDEX_FILE_PATH, "utf-8"), await fs.promises.readFile(INDEX_FILE_PATH, "utf-8"),
(key, value) => {
if (key === "id" || key === "source_graph_repo_id") {
return String(value);
}
return value;
},
); );
return await indexSchema.parseAsync(indexData); return await indexSchema.parseAsync(indexData);
} catch (err) { } catch (err) {

View File

@ -1,16 +1,16 @@
import { z } from "zod"; import { z } from "zod";
export const dependencySchema = z.object({ export const dependencySchema = z.object({
id: z.number().min(0), id: z.string(),
name: z.string(), name: z.string(),
}); });
export const repoSchema = z.object({ export const repoSchema = z.object({
id: z.number().min(0), id: z.string(),
url: z.string(), url: z.string(),
description: z.string(), description: z.string(),
stars: z.number().min(0), stars: z.number().min(0),
source_graph_repo_id: z.number().min(0), source_graph_repo_id: z.string(),
dependencies: z.array(dependencySchema), dependencies: z.array(dependencySchema),
last_checked_revision: z.nullable(z.string()), last_checked_revision: z.nullable(z.string()),
}); });

View File

@ -0,0 +1,55 @@
import {
Orama,
ProvidedTypes as OramaProvidedTypes,
create,
insertMultiple,
} from "@orama/orama";
import { Index } from "./schemas";
import { Context, createContext, useContext } from "react";
export interface IOramaContext<
OramaParameters extends Partial<OramaProvidedTypes> = any,
> {
isIndexed: boolean;
orama: Orama<OramaParameters> | null;
}
export function createOramaContext<
OramaParameters extends Partial<OramaProvidedTypes>,
>(): Context<IOramaContext<OramaParameters>> {
return createContext<IOramaContext<OramaParameters>>({
isIndexed: false,
orama: null,
});
}
export interface ReposOramaParameters extends Partial<OramaProvidedTypes> {
Index: { description: string };
}
export type ReposOrama = Orama<ReposOramaParameters>;
export async function createReposOrama(): Promise<ReposOrama> {
const orama = await create({
schema: {
description: "string",
},
id: "index",
});
return orama;
}
export async function prepareReposOramaIndex(
orama: ReposOrama,
data: Index["repos"],
): Promise<Orama<{ Index: { description: string } }>> {
await insertMultiple(orama, data, 100);
return orama;
}
export const ReposOramaContext = createOramaContext<ReposOramaParameters>();
ReposOramaContext.displayName = "ReposOramaContext";
export const useReposOrama = () => {
return useContext(ReposOramaContext);
};

View File

@ -1,6 +1,6 @@
import { type ClassValue, clsx } from "clsx" import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }