@wonderlandlabs/forestry-data-core
v1.0.1
Published
Forestry-backed collections and pluggable data connections for TypeScript applications.
Readme
@wonderlandlabs/forestry-data-core
Forestry-backed collections and pluggable data connections for TypeScript applications.
DataCore keeps mutable record collections in Forestry state, tracks local creates and changes, and gives applications a small connector boundary for loading, writing, and deleting records.
Status
DataCore is developed in the Timothy monorepo as an independently versioned public package.
Forestry Compatibility
DataCore uses the Forestry installation supplied by the application. Forestry is a peer dependency rather than a bundled runtime dependency, so DataCore can share one compatible Forestry instance with the rest of the application.
Install Forestry alongside DataCore:
pnpm add @wonderlandlabs/forestry-data-core \
@wonderlandlabs/forestry4DataCore supports Forestry >=4.1.15 <5 and is developed and tested against
Forestry 4.1.17. Applications may move to newer compatible Forestry 4
releases without waiting for a matching DataCore release.
Package Contents
CollectionBasestores records by string key and tracks created or changed records.DataManagerowns named collection branches and an optional data connector.DataConnectionManagertries data connections in order until one succeeds.JsonFixtureConnectionprovides an in-memory connection for examples and tests.- Connector types define the transport boundary without choosing an HTTP, database, or socket implementation.
- Change streams accept an application-owned event type.
Workspace Use
Add the package to another Timothy workspace package:
{
"dependencies": {
"@wonderlandlabs/forestry-data-core": "workspace:*"
}
}Import the public API from the package root:
import {
CollectionBase,
DataManager,
type CollectionBaseValue,
type DataManagerValue,
} from '@wonderlandlabs/forestry-data-core'Collections
Extend CollectionBase to add record-specific queries while retaining the
shared storage and change-tracking behavior.
import {
CollectionBase,
DataManager,
type CollectionBaseValue,
type DataManagerValue,
} from '@wonderlandlabs/forestry-data-core'
interface Book {
id: string
title: string
}
class BookCollection extends CollectionBase<Book> {
titled(title: string) {
return this.list().filter((book) => book.title === title)
}
}
interface LibraryValue extends DataManagerValue {
collections: Map<string, CollectionBaseValue<unknown>>
}
const data = new DataManager<LibraryValue>()
const books = data.addModel<string, Book, BookCollection>(
'books',
BookCollection,
)
books.setItem({
id: 'book-1',
title: 'The Left Hand of Darkness',
})
console.log(books.get('book-1'))
console.log(books.value.created.has('book-1'))Records use a string id by default. Pass keyOf to a collection constructor
when a record uses another string key.
Change Tracking
setItemandputtrack changes by default.setManyandputManydo not track changes by default, which makes them suitable for initial loads.- A newly tracked id appears in both
createdandchanged. deleteremoves the record and its tracking entries.clearTrackingpreserves records while clearingcreatedandchanged.
Data Connections
A DataConnector supplies asynchronous get, put, and delete operations.
Applications own the concrete transport.
import {
DataManager,
type DataConnector,
} from '@wonderlandlabs/forestry-data-core'
declare const connector: DataConnector
const data = new DataManager({ connector })
const books = await data.connector?.get<Book[]>('books')DataConnectionManager accepts ordered DataConnection instances. It calls
ensureReady before each operation and falls through to the next connection
when readiness or the operation fails.
JSON Fixtures
JsonFixtureConnection clones its input and keeps later writes isolated from
the original fixture object.
import {
DataManager,
JsonFixtureConnection,
} from '@wonderlandlabs/forestry-data-core'
const fixtures = new JsonFixtureConnection({
books: {
'book-1': {
id: 'book-1',
title: 'The Dispossessed',
},
},
})
const data = new DataManager({ connections: [fixtures] })
const book = await data.connector?.get<Book>('books', 'book-1')Fixture inputs may identify a record with a string id or an object containing a
string id. Calling get without an id returns all records for that model.
Lifecycle
Call start when a connector should begin publishing connection state or model
changes. Call stop once when the owning application is finished with it.
Development
pnpm --filter @wonderlandlabs/forestry-data-core test
pnpm --filter @wonderlandlabs/forestry-data-core lint
pnpm --filter @wonderlandlabs/forestry-data-core build