Skip to Content
Agentlang is open source now!
ConceptsResolvers

Resolvers

The Agentlang runtime persists entity instances to a backend store. By default this is a relational database like Sqlite or Postgres and can be configured via the config.al file. Examples:

{ "store": { "type": "sqlite", "dbname": "db" } }
{ "store": { "type": "postgres", "host": "#js process.env.POSTGRES_HOST || 'localhost'", "username": "#js process.env.POSTGRES_USER || 'postgres'", "password": "#js process.env.POSTGRES_PASSWORD || 'postgres'", "dbname": "#js process.env.POSTGRES_DB || 'testdb'", "port": "#js parseInt(process.env.POSTGRES_PORT || '5432')" } }

Agentlang also allows you to have entities reside in any third-party storage service. This is accomplished by writing a resolver. A resolver is a bunch of Javascript functions that take care of writing and reading entity instances from some backend-layer. As an illustration, consider a simple resolver that stores instances in an in-memory array:

resolver.js
const db = new Array() export function createInstance(resolver, inst) { db.push(inst) return inst } export async function queryInstances(resolver, inst, queryAll) { if (queryAll) { return db } else { const id = inst.lookupQueryVal('id') return db.filter((entry) => { return entry.lookup('id') === id }) } }

The resolver.js file exports two functions - one to add a new instance to the “database” and another to query instances from it. Now in an Agentlang module, you can attach this resolver to one or more entities.

acme.al
module acme entity Department { id Int @id name String } entity Employee { id Int @id, firstName String, lastName String, deptNo Int } import "resolver.js" @as r resolver acmeResolver [acme/Department, acme/Employee] { create r.createInstance, query r.queryInstances }

The key declaration here is that of the resolver acmeResolver. It’s attached to two entities - Department and Employee. Also note that resolver.js must be in the same directory as acme.al so that it can be imported by the runtime. Whenever a workflow pattern for creating or querying these entities are evaluated by the runtime, it will invoke the JavaScript functions for create and query respectively, instead of calling the default database API. Thus resolvers provide an easy way to integrate your application with third-party services.

The various methods that can be set for a resolver are listed below:

create - async function(resolver: Resolver, instanceToCreate: Instance): Promise<Instance> upsert - async function(resolver: Resolver, instanceToUpsert: Instance): Promise<Instance> update - async function(resolver: Resolver, instanceToUpdate: Instance, updatedAttributes: InstanceAttributes): Promise<Instance> delete - async function(resolver: Resolver, instanceOrInstancesToDelete :Instance | Instance[], purge: boolean): Promise<Instance | Instance[]> query - async function(resolver: Resolver, instanceWithQueryAttributes: Instance, queryAll: boolean): Promise<Instance[]> subscribe - async function(): Promise<any>` - start listening to incoming CRUD events (if the backend supports push-notifications) startTransaction - async function(resolver: Resolver): Promise<string>` - start a new transaction and return a unique transaction-id commitTransaction - async function(resolver: Resolver, transactionId: string): Promise<any> rollbackTransaction - async function(resolver: Resolver, transactionId: string): Promise<any>

For more advanced use-cases, you may want to directly implement the Resolver class which is the backbone of the resolver construct we discussed above. A general pattern for implementing and registering a Resolver with the runtime is shown below:

class MyResolver extends Resolver { // your implementation } function makeMyResolver() { return new MyResolver() } registerResolver("myResolver", makeMyResolver) setResolver("acme/employee", "myResolver")

registerResolver and setResolver are function defined in the Agentlang runtime’s resolver/registry  module.

Last updated on