Skip to main content

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.

declo-config.ts
.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.

declo-config.ts
.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.

declo-config.ts
.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.

declo-config.ts
.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:

declo-config.ts
.object({
id: (object) => object.id // or any other field that may be the identifier of your data
})

price <function>

declo-config.ts
.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:

declo-config.ts
.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:

declo-config.ts
.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

declo-config.ts
const ws = new WebSocket('wss://api.example.com')
declo-config.ts
.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()
}
}
})
})
IMPORTANT

Make sure to handle the WebSocket lifecycle properly, and close the connection when it's no longer needed.