@furystack/core
Version:
Core FuryStack package
146 lines (121 loc) • 4.74 kB
text/typescript
import type { Constructable } from '@furystack/inject'
import type { EventHub } from '@furystack/utils'
export const NumberComparisonOperators = ['$gt', '$gte', '$lt', '$lte'] as const
export const StringComparisonOperators = ['$startsWith', '$endsWith', '$like', '$regex'] as const
export const SingleComparisonOperators = ['$eq', '$ne'] as const
export const ArrayComparisonOperators = ['$in', '$nin'] as const
export const LogicalOperators = ['$and', '$not', '$nor', '$or'] as const
export const allOperators = [
...SingleComparisonOperators,
...NumberComparisonOperators,
...ArrayComparisonOperators,
...LogicalOperators,
...StringComparisonOperators,
] as const
export type FilterType<T> = {
[K in keyof T]?:
| (T[K] extends string ? { [SCO in (typeof StringComparisonOperators)[number]]?: T[K] } : never)
| (T[K] extends number ? { [SCO in (typeof NumberComparisonOperators)[number]]?: T[K] } : never)
| { [SCO in (typeof SingleComparisonOperators)[number]]?: T[K] }
| { [ACO in (typeof ArrayComparisonOperators)[number]]?: Array<T[K]> }
} & { [LO in (typeof LogicalOperators)[number]]?: Array<FilterType<T>> }
export const isLogicalOperator = (propertyString: string): propertyString is (typeof LogicalOperators)[number] =>
LogicalOperators.includes(propertyString as (typeof LogicalOperators)[number])
export const isOperator = (propertyString: string): propertyString is (typeof allOperators)[number] =>
allOperators.includes(propertyString as (typeof allOperators)[number])
export const t: FilterType<{ a: number; b: string; c: boolean }> = {
a: { $eq: 3 },
b: { $in: ['a', 'b', 'c'] },
$and: [{ a: { $eq: 2 } }],
}
export interface CreateResult<T> {
created: T[]
}
export type WithOptionalId<T, TPrimaryKey extends keyof T> = Omit<T, TPrimaryKey> & { [K in TPrimaryKey]?: T[K] }
/**
* Type for default filtering model
*/
export interface FindOptions<T, TSelect extends Array<keyof T>> {
/**
* Limits the hits
*/
top?: number
/**
* Skips the first N hit
*/
skip?: number
/**
* Sets up an order by a field and a direction
*/
order?: { [P in keyof T]?: 'ASC' | 'DESC' }
/**
* The result set will be limited to these fields
*/
select?: TSelect
/**
* The fields should match this filter
*/
filter?: FilterType<T>
}
export type PartialResult<T, TFields extends Array<keyof T>> = Pick<T, TFields[number]>
export const selectFields = <T extends object, TField extends Array<keyof T>>(entry: T, ...fields: TField) => {
const returnValue = {} as PartialResult<T, TField>
Object.keys(entry).map((key) => {
const field: TField[number] = key as TField[number]
if (fields.includes(field)) {
returnValue[field] = entry[field]
}
})
return returnValue
}
/**
* Interface that defines a physical store implementation
*/
export interface PhysicalStore<T, TPrimaryKey extends keyof T, TWriteableData = WithOptionalId<T, TPrimaryKey>>
extends EventHub<{
onEntityAdded: { entity: T }
onEntityUpdated: { id: T[TPrimaryKey]; change: Partial<T> }
onEntityRemoved: { key: T[TPrimaryKey] }
}> {
/**
* The Primary key field name
*/
readonly primaryKey: TPrimaryKey
/**
* A constructable model
*/
readonly model: Constructable<T>
/**
* Adds an entry to the store, returns a promise that will be resolved with the added data
* @param entries The data to be added
*/
add(...entries: TWriteableData[]): Promise<CreateResult<T>>
/**
* Updates an entry in the store, returns a promise that will be resolved once the update is done
* @param id The primary key of the entry
* @param data The data to be updated
*/
update(id: T[TPrimaryKey], data: Partial<T>): Promise<void>
/**
* Returns a promise that will be resolved with the count of the elements
*/
count(filter?: FilterType<T>): Promise<number>
/**
* Returns a promise that will be resolved with an array of elements that matches the filter
* @param searchOptions An options object for the Search expression
*/
find<TSelect extends Array<keyof T>>(findOptions: FindOptions<T, TSelect>): Promise<Array<PartialResult<T, TSelect>>>
/**
* Returns a promise that will be resolved with an entry with the defined primary key or undefined
* @param key The primary key of the entry
*/
get<TSelect extends Array<keyof T>>(
key: T[TPrimaryKey],
select?: TSelect,
): Promise<PartialResult<T, TSelect> | undefined>
/**
* Removes an entry with the defined primary key. Returns a promise that will be resolved once the operation is completed
* @param key The primary key of the entry to remove
*/
remove(...keys: Array<T[TPrimaryKey]>): Promise<void>
}