Utilities
import {
serializeValue,
get,
isEqual,
modify,
randomId,
EventEmitter,
reactiveOrAsync,
unwrap,
} from '@signaldb/core'@signaldb/core exports the helpers it uses internally that are useful when you write an adapter or an integration of your own. They follow the same semantics SignalDB applies itself, so an adapter built on them cannot disagree with SignalDB about what a value or a modifier means.
serializeValue(value: any): string | null
Turns a field value into the key an index is stored under: strings as they are, numbers and booleans as their string form, dates as ISO strings, everything else through JSON.stringify. null and undefined both become null.
A custom StorageAdapter must key the map returned by readIndex with exactly this function — it is what SignalDB looks an index up with.
serializeValue(3) // '3'
serializeValue(new Date(0)) // '1970-01-01T00:00:00.000Z'
serializeValue(undefined) // nullget(value: object, path: string)
Reads the value at a path inside an object. Dot notation ('author.name') and bracket notation ('tags[0]') are supported. Returns undefined if any part of the path does not exist or the path is malformed.
get({ author: { name: 'Ada' } }, 'author.name') // 'Ada'isEqual(a, b): boolean
Compares two values for deep equality, including arrays, dates and regular expressions.
isEqual({ a: [1, new Date(0)] }, { a: [1, new Date(0)] }) // truemodify(item: T, modifier: Modifier): T
Applies a modifier to an item and returns the modified copy; the input is not mutated. A modifier without any $ operator replaces the item as a whole, as it does in updateOne.
modify({ a: 1, b: 2 }, { $set: { b: 3 } }) // { a: 1, b: 3 }randomId(): string
Returns a random id of 16 lowercase alphanumeric characters — the default primary key of a collection. It uses Math.random() and is therefore not suitable where an id must be unguessable.
EventEmitter
The typed event emitter Collection is built on. Extend it with a map of event names to listener signatures:
class Store extends EventEmitter<{
saved: (id: string) => void,
}> {}
const store = new Store()
store.on('saved', id => console.log(id))
store.emit('saved', 'a1')It offers on/addListener, once, off/removeListener, emit, listeners, listenerCount, removeAllListeners and setMaxListeners. It logs a warning whenever an event has more listeners than the maximum, 100 by default, which usually points at listeners that are never removed.
reactiveOrAsync(generator) and unwrap(value)
Build a function that can be called both ways SignalDB queries can: synchronously — and therefore reactively — or asynchronously with { async: true } as its last argument. Write the logic once as a generator and yield* unwrap(…) every value that may be a promise; reactiveOrAsync runs it synchronously or awaits each yielded promise, depending on how it was called.
const getPostWithAuthor = reactiveOrAsync(function* (async: boolean, postId: string) {
const post = yield* unwrap(Posts.findOne({ id: postId }, { async }))
if (!post) return undefined
const author = yield* unwrap(Authors.findOne({ id: post.authorId }, { async }))
return { ...post, author }
})
getPostWithAuthor('p1') // synchronous and reactive
await getPostWithAuthor('p1', { async: true }) // asynchronousThe generator receives whether it runs asynchronously as its first argument; pass it on to the queries it makes. Yielding a promise in synchronous mode is a programming error and throws. The returned function exposes its generator as .generator, so one such workflow can compose another with yield* other.generator.call(this, async, …).
A collection's transformAll can be built the same way: SignalDB calls it with { async: true } for an asynchronous read and synchronously for every other one.