Skip to content

Commit

Permalink
fixup! feat: added strong typed orama schema
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad committed Sep 6, 2023
1 parent a369ac7 commit 682a3e8
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 99 deletions.
2 changes: 1 addition & 1 deletion packages/orama/src/components/facets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function sortingPredicate(order: FacetSorting = 'desc', a: [string, number], b:
export async function getFacets<T extends AnyOrama>(
orama: T,
results: TokenScore[],
facetsConfig: FacetsParams,
facetsConfig: FacetsParams<T>,
): Promise<FacetResult> {
const facets: FacetResult = {}
const allIDs = results.map(([id]) => id)
Expand Down
2 changes: 1 addition & 1 deletion packages/orama/src/components/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const ALLOWED_TYPES = ['string', 'number', 'boolean']
export async function getGroups<T extends AnyOrama, ResultDocument = TypedDocument<T>>(
orama: T,
results: TokenScore[],
groupBy: GroupByParams<ResultDocument>,
groupBy: GroupByParams<T, ResultDocument>,
): Promise<GroupResult<ResultDocument>> {
const properties = groupBy.properties
const propertiesLength = properties.length
Expand Down
7 changes: 4 additions & 3 deletions packages/orama/src/methods/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
CustomSorterFunctionItem,
ElapsedTime,
IndexMap,
LiteralUnion,
Result,
Results,
SearchContext,
Expand Down Expand Up @@ -132,8 +133,8 @@ export async function search<T extends AnyOrama, ResultDocument = TypedDocument<

if (properties && properties !== '*') {
for (const prop of properties) {
if (!propertiesToSearch.includes(prop)) {
throw createError('UNKNOWN_INDEX', prop, propertiesToSearch.join(', '))
if (!propertiesToSearch.includes(prop as string)) {
throw createError('UNKNOWN_INDEX', prop as string, propertiesToSearch.join(', '))
}
}

Expand Down Expand Up @@ -284,7 +285,7 @@ async function fetchDocumentsWithDistinct<T extends AnyOrama, ResultDocument ext
uniqueDocsArray: [InternalDocumentID, number][],
offset: number,
limit: number,
distinctOn: string,
distinctOn: LiteralUnion<T['schema']>,
): Promise<Result<ResultDocument>[]> {
const docs = orama.data.docs

Expand Down
28 changes: 15 additions & 13 deletions packages/orama/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type SchemaTypes<Value> = Value extends 'string'
: Value extends 'number[]'
? number[]
: // eslint-disable-next-line @typescript-eslint/no-unused-vars
Value extends `vector[${infer Size extends number}]`
Value extends `vector[${number}]`
? number[]
: Value extends object
? { [Key in keyof Value]: SchemaTypes<Value[Key]> } & {
Expand Down Expand Up @@ -109,7 +109,7 @@ export interface BooleanFacetDefinition {
false?: boolean
}

export type FacetsParams = Record<string, FacetDefinition>
export type FacetsParams<T extends AnyOrama> = Partial<Record<LiteralUnion<T['schema']>, FacetDefinition>>

export type FacetDefinition = StringFacetDefinition | NumberFacetDefinition | BooleanFacetDefinition

Expand All @@ -119,10 +119,10 @@ export type Reduce<T, R = AnyDocument> = {
getInitialValue: (elementCount: number) => T
}

export type GroupByParams<T> = {
properties: string[]
export type GroupByParams<T extends AnyOrama, ResultDocument> = {
properties: LiteralUnion<T['schema']>[]
maxResult?: number
reduce?: Reduce<T>
reduce?: Reduce<ResultDocument>
}

export type ComparisonOperator = {
Expand All @@ -143,14 +143,16 @@ export type CustomSorterFunction<ResultDocument> = (
a: CustomSorterFunctionItem<ResultDocument>,
b: CustomSorterFunctionItem<ResultDocument>,
) => number
// thanks to https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts
export type LiteralUnion<T> = (keyof T extends string ? keyof T : never) | (string & Record<never, never>)
/**
* Define which properties to sort for.
*/
export type SorterParams<T extends AnyOrama> = {
/**
* The key of the document used to sort the result.
*/
property: keyof T['schema'] extends string ? keyof T['schema'] : string;
property: LiteralUnion<T['schema']>;
/**
* Whether to sort the result in ascending or descending order.
*/
Expand All @@ -167,7 +169,7 @@ export type SearchParams<T extends AnyOrama, ResultDocument = TypedDocument<T>>
/**
* The properties of the document to search in.
*/
properties?: '*' | string[]
properties?: '*' | LiteralUnion<T['schema']>[]
/**
* The number of matched documents to return.
*/
Expand Down Expand Up @@ -226,7 +228,7 @@ export type SearchParams<T extends AnyOrama, ResultDocument = TypedDocument<T>>
*
* // In that case, the score of the 'title' property will be multiplied by 2.
*/
boost?: Record<string, number>
boost?: Partial<Record<LiteralUnion<T['schema']>, number>>
/**
* Facets configuration
* Full documentation: https://docs.oramasearch.com/usage/search/facets
Expand All @@ -248,7 +250,7 @@ export type SearchParams<T extends AnyOrama, ResultDocument = TypedDocument<T>>
* }
* });
*/
facets?: FacetsParams
facets?: FacetsParams<T>

/**
* Distinct configuration
Expand All @@ -260,7 +262,7 @@ export type SearchParams<T extends AnyOrama, ResultDocument = TypedDocument<T>>
* distinctOn: 'category.primary',
* })
*/
distinctOn?: string
distinctOn?: LiteralUnion<T['schema']>

/**
* Groups configuration
Expand All @@ -275,7 +277,7 @@ export type SearchParams<T extends AnyOrama, ResultDocument = TypedDocument<T>>
* }
* })
*/
groupBy?: GroupByParams<ResultDocument>
groupBy?: GroupByParams<T, ResultDocument>

/**
* Filter the search results.
Expand All @@ -295,7 +297,7 @@ export type SearchParams<T extends AnyOrama, ResultDocument = TypedDocument<T>>
* }
* });
*/
where?: Record<string, boolean | string | string[] | ComparisonOperator>
where?: Partial<Record<LiteralUnion<T['schema']>, boolean | string | string[] | ComparisonOperator>>

/**
* Threshold to use for refining the search results.
Expand Down Expand Up @@ -543,7 +545,7 @@ export interface IIndex<I extends AnyIndexStore> {
searchByWhereClause<T extends AnyOrama, ResultDocument = TypedDocument<T>>(
context: SearchContext<T, ResultDocument>,
index: I,
filters: Record<string, boolean | string | string[] | ComparisonOperator>,
filters: Partial<Record<LiteralUnion<T['schema']>, boolean | string | string[] | ComparisonOperator>>,
): SyncOrAsyncValue<InternalDocumentID[]>

getSearchableProperties(index: I): SyncOrAsyncValue<string[]>
Expand Down
11 changes: 4 additions & 7 deletions packages/orama/tests/sort.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import t from 'tap'
import { CustomSorterFunctionItem, create, insert, insertMultiple, load, remove, save, search } from '../src/index.js'
import { create, insert, insertMultiple, load, remove, save, search } from '../src/index.js'

t.test('search with sortBy', t => {
t.test('on number', async t => {
Expand Down Expand Up @@ -368,7 +368,7 @@ t.test('search with sortBy', t => {
number: 'number',
},
})
await t.rejects(search(db, { sortBy: { property: 'foobar' } }))
await t.rejects(search(db, { sortBy: { property: 'foobar' } as any }))

t.end()
})
Expand All @@ -388,9 +388,6 @@ t.test('search with sortBy', t => {
})

t.test('should allow custom function', async t => {
interface Doc {
string?: string
}
const db = await create({
schema: {
string: 'string',
Expand All @@ -406,8 +403,8 @@ t.test('search with sortBy', t => {
])

const result = await search(db, {
sortBy: (a: CustomSorterFunctionItem, b: CustomSorterFunctionItem) => {
return ((a[2] as unknown as Doc).string || '').localeCompare((b[2] as unknown as Doc).string || '')
sortBy: (a, b) => {
return (a[2].string || '').localeCompare(b[2].string || '')
},
})

Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-match-highlight/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async function recursivePositionInsertion<T extends AnyOrama, ResultDocument = T
}

export async function searchWithHighlight<T extends AnyOrama, ResultDocument = TypedDocument<T>>(
orama: OramaWithHighlight<T>,
orama: T,
params: SearchParams<T, ResultDocument>,
language?: Language
): Promise<SearchResultWithHighlight<ResultDocument>> {
Expand All @@ -92,7 +92,7 @@ export async function searchWithHighlight<T extends AnyOrama, ResultDocument = T
const hits = result.hits.map((hit: AnyDocument) =>
Object.assign(hit, {
positions: Object.fromEntries(
Object.entries<any>(orama.data.positions[hit.id]).map(([propName, tokens]) => [
Object.entries<any>((orama as OramaWithHighlight<T>).data.positions[hit.id]).map(([propName, tokens]) => [
propName,
Object.fromEntries(
Object.entries(tokens).filter(([token]) => queryTokens.find(queryToken => token.startsWith(queryToken)))
Expand Down
144 changes: 72 additions & 72 deletions packages/plugin-match-highlight/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,105 +1,105 @@
import { create, insert } from '@orama/orama'
import t from 'tap'
import { create, insert } from '@orama/orama';
import t from 'tap';
import {
afterInsert,
loadWithHighlight,
OramaWithHighlight,
saveWithHighlight,
searchWithHighlight
} from '../src/index.js'
afterInsert,
loadWithHighlight,
OramaWithHighlight,
saveWithHighlight,
searchWithHighlight,
} from '../src/index.js';

t.test('it should store the position of tokens', async t => {
const schema = {
text: 'string'
} as const
const db = await create({
schema: {
text: 'string',
}, components: { afterInsert: (orama, key, doc) => afterInsert(orama, key) },
});

const db = (await create({ schema, components: { afterInsert } })) as OramaWithHighlight
const id = await insert(db, { text: 'hello world' });

const id = await insert(db, { text: 'hello world' })

t.same(db.data.positions[id], {
text: { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] }
})
})
t.same((db as OramaWithHighlight<typeof db>).data.positions[id], {
text: { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] },
});
});

t.test('it should manage nested schemas', async t => {
const schema = {
other: {
text: 'string'
}
} as const
const schema = {
other: {
text: 'string',
},
} as const;

const db = (await create({ schema, components: { afterInsert } })) as OramaWithHighlight
const db = (await create({ schema, components: { afterInsert: (orama, key, doc) => afterInsert(orama, key) } }));

const id = await insert(db, { other: { text: 'hello world' } })
const id = await insert(db, { other: { text: 'hello world' } });

t.same(db.data.positions[id], {
'other.text': { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] }
})
})
t.same((db as OramaWithHighlight<typeof db>).data.positions[id], {
'other.text': { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] },
});
});

t.test("it shouldn't stem tokens", async t => {
const schema = {
text: 'string'
} as const
t.test('it shouldn\'t stem tokens', async t => {
const schema = {
text: 'string',
} as const;

const db = (await create({
schema,
components: { afterInsert, tokenizer: { stemming: false } }
})) as OramaWithHighlight
const db = (await create({
schema,
components: { afterInsert: (orama, key, doc) => afterInsert(orama, key), tokenizer: { stemming: false } },
}));

const id = await insert(db, { text: 'hello personalization' })
const id = await insert(db, { text: 'hello personalization' });

t.same(db.data.positions[id], {
text: { hello: [{ start: 0, length: 5 }], personalization: [{ start: 6, length: 15 }] }
})
})
t.same((db as OramaWithHighlight<typeof db>).data.positions[id], {
text: { hello: [{ start: 0, length: 5 }], personalization: [{ start: 6, length: 15 }] },
});
});

t.test('should retrieve positions', async t => {
const schema = {
text: 'string'
} as const
const schema = {
text: 'string',
} as const;

const db = (await create({ schema, components: { afterInsert } })) as OramaWithHighlight
const db = (await create({ schema, components: { afterInsert } }));

await insert(db, { text: 'hello world' })
await insert(db, { text: 'hello world' });

const results = await searchWithHighlight(db, { term: 'hello' })
t.same(results.hits[0].positions, { text: { hello: [{ start: 0, length: 5 }] } })
})
const results = await searchWithHighlight(db, { term: 'hello' });
t.same(results.hits[0].positions, { text: { hello: [{ start: 0, length: 5 }] } });
});

t.test('should work with texts containing constructor and __proto__ properties', async t => {
const schema = {
text: 'string'
} as const
const schema = {
text: 'string',
} as const;

const db = (await create({ schema, components: { afterInsert } })) as OramaWithHighlight
const db = (await create({ schema, components: { afterInsert } }));

await insert(db, { text: 'constructor __proto__' })
await insert(db, { text: 'constructor __proto__' });

const results = await searchWithHighlight(db, { term: 'constructor' })
const results = await searchWithHighlight(db, { term: 'constructor' });

t.same(results.hits[0].positions, {
text: { constructor: [{ start: 0, length: 11 }] }
})
})
t.same(results.hits[0].positions, {
text: { constructor: [{ start: 0, length: 11 }] },
});
});

t.test('should correctly save and load data with positions', async t => {
const schema = {
text: 'string'
} as const
const schema = {
text: 'string',
} as const;

const originalDB = (await create({ schema, components: { afterInsert } })) as OramaWithHighlight
const originalDB = (await create({ schema, components: { afterInsert } }));

const id = await insert(originalDB, { text: 'hello world' })
const id = await insert(originalDB, { text: 'hello world' });

const DBData = await saveWithHighlight(originalDB)
const DBData = await saveWithHighlight(originalDB);

const newDB = (await create({ schema, components: { afterInsert } })) as OramaWithHighlight
const newDB = (await create({ schema, components: { afterInsert } }));

await loadWithHighlight(newDB, DBData)
await loadWithHighlight(newDB, DBData);

t.same(newDB.data.positions[id], {
text: { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] }
})
})
t.same((newDB as OramaWithHighlight<typeof newDB>).data.positions[id], {
text: { hello: [{ start: 0, length: 5 }], world: [{ start: 6, length: 5 }] },
});
});

0 comments on commit 682a3e8

Please sign in to comment.