Objects
Overview
Objects is the fundamental building block of declo, and is the representation of a "thing" in your system. declo makes no assumptions about your structure, but objects are often products, downloads, licenses, delivery, shipments, vouchers and other objects that are usually added or assigned to a basket.
Defining objects
An object is defined by the interface:
interface Object<Source> {
type: string;
single?: boolean;
source: (ids: (string|number)[]) => Source[] | Promise<Source[]> | Subscribable<Source>;
id: (object: Source) => string;
price: (object: Source) => number;
revalidate?: (object: Source) => boolean;
}
type <string>
The type
field is the unique identifier of your object's type.
This could be product
, delivery
, payment
, or any other entity you want to represent.
.object({
type: 'YOUR_TYPE'
})
single <boolean>
The single
field determines if an object can occur multiple times within a basket.
An object like product
is usually not single, while a type such as delivery
is.
If an object with single: true
is added to the basket, it will replace any existing object of the same type,
and the basket will always contain at most one object of that type.
By default objects are not marked as single.
.object({
single: true
})
source <function>
Objects in itself are just a rough outline of a "thing", so we'll need to define the source of the object data.
Sourcing is done through a source
function, whose responsibility is to provide the necessary data for the requested ids.
Where the data originates from is up to you, it could be a database, an API, or any other source.
A source
function can be async or even stream based in form of an Observable.
See the sourcing section a more in-depth explanation.
.object({
source: (ids) => ids.map(id => ({ id, price: 0 })),
})
revalidate <function>
Object sourcing is a frequent operation, to control this frequency a revalidation function can be provided to determine if an object is eligible for sourcing.
.object({
revalidate: (object) => {
return !object ||
// Revalidate if the object is older than 30 minutes
Math.abs(object.lastUpdate.getTime() - new Date().getTime()) > 1000 * 60 * 30
},
})
id <function>
Since declo makes no assumptions about your object structure, but need some specifics about your data, you'll need to provide a couple of getter style functions that based on your source data can extract the necessary information.
The id
field is required to be able to identify your object data:
.object({
id: (object) => object.id // or any other field that may be the identifier of your data
})
price <function>
.object({
id: (object) => object.id, // you decide how to identify your object
})
Sourcing
Sourcing is one of the core concepts of objects, ...
Async
Since sourcing typically means retrieving data from an external source, your source function can be async returning a promise.
An example could be using fetch
to retrieve data from an API:
.object({
source: async (ids) => {
const url = new URL('https://api.example.com?items=')
ids.forEach(id => url.searchParams.append('id', id))
return fetch(url.toString()).then(response => response.json())
}
})
Stream
In some cases, you might want to use a stream based approach to sourcing data. This is done by returning a subscribable signature from the source function:
interface Subscribable<T> {
subscribe(observer: {
next: (value: T) => void,
error: (error: any) => void,
complete: () => void
}): { unsubscribe: () => void }
}
An example of a stream based source function could look like this:
.object({
source: (ids) => {
return {
subscribe: ({
next,
error,
complete
}) => {
return {
// ... setup logic
unsubscribe: () => {
// ... teardown logic
}
}
}
}
}
})
Using WebSockets
Setup a WebSocket connection at top level of your configuration module
const ws = new WebSocket('wss://api.example.com')
.object({
source: (ids) => ({
subscribe: ({ next, error, complete }) => {
ws.onclose = () => complete()
ws.onerror = (event) => error(event)
ws.onmessage = (event) => next(JSON.parse(event.data))
return {
unsubscribe: () => ws.close()
}
}
})
})
Make sure to handle the WebSocket lifecycle properly, and close the connection when it's no longer needed.