@logux/client
Version:
Logux base components to build web client
325 lines (298 loc) • 8.15 kB
TypeScript
import type { SyncMapValues } from '@logux/actions'
import type { Action, Meta } from '@logux/core'
import type { MapCreator, MapStore, ReadableAtom, StoreValue } from 'nanostores'
import type { Client } from '../client/index.js'
import type { FilterValue, LoadedFilterValue } from '../create-filter/index.js'
interface SyncMapStoreExt {
/**
* Logux Client instance.
*/
readonly client: Client
/**
* Meta from create action if the store was created locally.
*/
createdAt?: Meta
/**
* Mark that store was deleted.
*/
deleted?: true
/**
* While store is loading initial data from server or log.
*/
readonly loading: Promise<void>
/**
* Does store keep data in the log after store is destroyed.
*/
offline: boolean
/**
* Name of map class.
*/
readonly plural: string
/**
* Does store use server to load and save data.
*/
remote: boolean
}
export type LoadedSyncMapValue<Value extends SyncMapValues> = {
id: string
isLoading: false
} & Value
export type SyncMapValue<Value extends SyncMapValues> =
| { id: string; isLoading: true }
| LoadedSyncMapValue<Value>
export type SyncMapStore<Value extends SyncMapValues = any> = MapStore<
SyncMapValue<Value>
> &
SyncMapStoreExt
export interface SyncMapTemplate<
Value extends SyncMapValues = any,
StoreExt = object
> extends MapCreator {
(
id: string,
client: Client,
...args: [] | [Action, Meta, boolean | undefined]
): StoreExt & SyncMapStore<Value>
cache: {
[id: string]: StoreExt & SyncMapStore<Value>
}
offline: boolean
readonly plural: string
remote: boolean
}
export interface SyncMapTemplateLike<
Value extends object = any,
Args extends any[] = []
> {
(id: string, client: Client, ...args: Args): MapStore<Value>
}
/**
* CRDT LWW Map. It can use server validation or be fully offline.
*
* The best option for classic case with server and many clients.
* Store will resolve client’s edit conflicts with last write wins strategy.
*
* ```ts
* import { syncMapTemplate } from '@logux/client'
*
* export const User = syncMapTemplate<{
* login: string,
* name?: string,
* isAdmin: boolean
* }>('users')
* ```
*
* @param plural Plural store name. It will be used in action type
* and channel name.
* @param opts Options to disable server validation or keep actions in log
* for offline support.
*/
export function syncMapTemplate<Value extends SyncMapValues>(
plural: string,
opts?: {
offline?: boolean
remote?: boolean
}
): SyncMapTemplate<Value>
/**
* Send create action to the server or to the log.
*
* Server will create a row in database on this action. {@link FilterStore}
* will update the list.
*
* ```js
* import { createSyncMap } from '@logux/client'
*
* showLoader()
* await createSyncMap(client, User, {
* id: nanoid(),
* login: 'test'
* })
* hideLoader()
* ```
*
* @param client Logux Client instance.
* @param Template Store template from {@link syncMapTemplate}.
* @param value Initial value.
* @return Promise until server validation for remote classes
* or saving action to the log of fully offline classes.
*/
export function createSyncMap<Value extends SyncMapValues>(
client: Client,
Template: SyncMapTemplate<Value>,
value: { id: string } & Value
): Promise<void>
/**
* Send create action and build store instance.
*
* ```js
* import { buildNewSyncMap } from '@logux/client'
*
* let userStore = buildNewSyncMap(client, User, {
* id: nanoid(),
* login: 'test'
* })
* ```
*
* @param client Logux Client instance.
* @param Template Store template from {@link syncMapTemplate}.
* @param value Initial value.
* @return Promise with store instance.
*/
export function buildNewSyncMap<Value extends SyncMapValues>(
client: Client,
Template: SyncMapTemplate<Value>,
value: { id: string } & Value
): Promise<SyncMapStore<Value>>
/**
* Change store without store instance just by store ID.
*
* ```js
* import { changeSyncMapById } from '@logux/client'
*
* let userStore = changeSyncMapById(client, User, 'user:4hs2jd83mf', {
* name: 'New name'
* })
* ```
*
* @param client Logux Client instance.
* @param Template Store template from {@link syncMapTemplate}.
* @param id Store’s ID.
* @param diff Store’s changes.
* @return Promise until server validation for remote classes
* or saving action to the log of fully offline classes.
*/
export function changeSyncMapById<Value extends SyncMapValues>(
client: Client,
Template: SyncMapTemplate<Value>,
id: string,
diff: Partial<Value>
): Promise<void>
export function changeSyncMapById<
Value extends SyncMapValues,
ValueKey extends keyof Value
>(
client: Client,
Template: SyncMapTemplate<Value>,
id: string,
key: ValueKey,
value: Value[ValueKey]
): Promise<void>
/**
* Change keys in the store’s value.
*
* ```js
* import { changeSyncMap } from '@logux/client'
*
* showLoader()
* await changeSyncMap(userStore, { name: 'New name' })
* hideLoader()
* ```
*
* @param store Store’s instance.
* @param diff Store’s changes.
* @return Promise until server validation for remote classes
* or saving action to the log of fully offline classes.
*/
export function changeSyncMap<Value extends SyncMapValues>(
store: SyncMapStore<Value>,
diff: Partial<Omit<Value, 'id'>>
): Promise<void>
export function changeSyncMap<
Value extends SyncMapValues,
ValueKey extends Exclude<keyof Value, 'id'>
>(
store: SyncMapStore<Value>,
key: ValueKey,
value: Value[ValueKey]
): Promise<void>
/**
* Delete store without store instance just by store ID.
*
* ```js
* import { deleteSyncMapById } from '@logux/client'
*
* showLoader()
* await deleteSyncMapById(client, User, 'user:4hs2jd83mf')
* ```
*
* @param client Logux Client instance.
* @param Template Store template from {@link syncMapTemplate}.
* @param id Store’s ID.
* @return Promise until server validation for remote classes
* or saving action to the log of fully offline classes.
*/
export function deleteSyncMapById(
client: Client,
Template: SyncMapTemplate,
id: string
): Promise<void>
/**
* Delete store.
*
* ```js
* import { deleteSyncMap } from '@logux/client'
*
* showLoader()
* await deleteSyncMap(User)
* ```
*
* @param store Store’s instance.
* @return Promise until server validation for remote classes
* or saving action to the log of fully offline classes.
*/
export function deleteSyncMap(store: SyncMapStore): Promise<void>
/**
* Change store’s value type to value with `isLoaded: false`.
*
* If store is still loading, this function will trow an error.
*
* Use it for tests written on TypeScript.
*
* ```js
* import { ensureLoaded } from '@logux/client'
*
* expect(ensureLoaded($currentUser)).toEqual({ id: 1, name: 'User' })
* ```
*
* @param value Store’s value.
*/
export function ensureLoaded<Value extends SyncMapValues>(
value: SyncMapValue<Value>
): LoadedSyncMapValue<Value>
export function ensureLoaded<Value extends SyncMapValues>(
value: FilterValue<Value>
): LoadedFilterValue<Value>
export type LoadedValue<Type extends { isLoading: boolean }> = {
isLoading: false
} & Type
type LoadableStore = {
readonly loading: Promise<unknown>
} & ReadableAtom<{ isLoading: boolean }>
/**
* Return store’s value if store is loaded or wait until store will be loaded
* and return its value.
*
* Returns `undefined` on 404.
*
* ```js
* import { loadValue } from '@logux/client'
*
* let user = loadValue($currentUser)
* ```
*
* @param store Store to load.
*/
export function loadValue<Store extends SyncMapStore>(
store: Store
): Promise<LoadedValue<StoreValue<Store>> | undefined>
export function loadValue<Store extends LoadableStore>(
store: Store
): Promise<LoadedValue<StoreValue<Store>>>
export type LoadedSyncMap<Store extends SyncMapStore> = MapStore<
LoadedSyncMapValue<StoreValue<Store>>
> &
SyncMapStoreExt
export function ensureLoadedStore<Store extends SyncMapStore>(
store: Store
): LoadedSyncMap<Store>