Node.js Developer Guide
Other documentationsDemoCommunityGitHub
  • Forest Admin
  • Getting started
    • How it works
    • Quick start
    • Install
      • Create your agent
      • Expose an HTTP endpoint
        • For standalone agents
        • On Express
        • On Koa
        • On Fastify
        • On NestJS
      • Autocompletion & Typings
      • Troubleshooting
    • Migrating legacy agents
      • What's new
      • Pre-requisites
      • Recommendations
      • Migration steps
        • Run new agent in parallel
        • Configure database connection
        • Code transformations
          • API Charts
          • Live Queries
          • Smart Charts
          • Route overrides
          • Smart Actions
          • Smart Fields
          • Smart Relationships
          • Smart Segments
        • Compare schemas
        • Swap agents
      • Post-migration
        • Dropping Sequelize
        • Optimize your agent
  • Data Sources
    • Getting Started
      • Collection selection
      • Naming conflicts
      • Cross-data source relationships
      • Query interface and Native Queries
        • Fields and projections
        • Filters
        • Aggregations
    • Provided data sources
      • SQL (without ORM)
      • Sequelize
      • Mongoose
      • MongoDB
    • Write your own
      • Replication strategy
        • Persistent cache
        • Updating the replica
          • Scheduled rebuilds
          • Change polling
          • Push & Webhooks
        • Schema & References
        • Write handlers
      • Translation strategy
        • Structure declaration
        • Capabilities declaration
        • Read implementation
        • Write implementation
        • Intra-data source Relationships
      • Contribute
  • Agent customization
    • Getting Started
    • Actions
      • Scope and context
      • Result builder
      • Static Forms
      • Widgets in Forms
      • Dynamic Forms
      • Form layout customization
      • Related data invalidation
    • Charts
      • Value
      • Objective
      • Percentage
      • Distribution
      • Leaderboard
      • Time-based
    • Fields
      • Add fields
      • Move, rename and remove fields
      • Override binary field mode
      • Override writing behavior
      • Override filtering behavior
      • Override sorting behavior
      • Validation
    • Hooks
      • Collection hook
      • Collection override
    • Pagination
    • Plugins
      • Provided plugins
        • AWS S3
        • Advanced Export
        • Flattener
      • Write your own
    • Relationships
      • To a single record
      • To multiple records
      • Computed foreign keys
      • Under the hood
    • Search
    • Segments
  • Frontend customization
    • Smart Charts
      • Create a table chart
      • Create a bar chart
      • Create a cohort chart
      • Create a density map
    • Smart Views
      • Create a Map view
      • Create a Calendar view
      • Create a Shipping view
      • Create a Gallery view
      • Create a custom tinder-like validation view
      • Create a custom moderation view
  • Deploying to production
    • Environments
      • Deploy on AWS
      • Deploy on Heroku
      • Deploy on GCP
      • Deploy on Ubuntu
      • Deploy on Azure
    • Development workflow
    • Using branches
    • Deploying your changes
    • Forest Admin CLI commands
      • init
      • login
      • branch
      • switch
      • set-origin
      • push
      • environments:create
      • environments:reset
      • deploy
  • Under the hood
    • .forestadmin-schema.json
    • Data Model
      • Typing
      • Relationships
    • Security & Privacy
Powered by GitBook
On this page
  • Choosing when to poll for changes
  • Programming your handler
  • Example

Was this helpful?

  1. Data Sources
  2. Write your own
  3. Replication strategy
  4. Updating the replica

Change polling

PreviousScheduled rebuildsNextPush & Webhooks

Last updated 1 year ago

Was this helpful?

This is the official documentation of the @forestadmin/agent Node.js agent.

Fetching all the data from the target API at each update is not always possible, as it may be too slow, too expensive, or over the API rate limit.

Another strategy is to poll for changes on the target API and only fetch the records that changed.

This strategy is called "change polling".

Choosing when to poll for changes

The following events are available:

  • pullDeltaOnRestart: When true, your handler will be called on each agent restart

  • pullDeltaOnSchedule: if set to a cron-like schedule, your handler will be called on that schedule. The syntax is the same as for pullDumpOnSchedule

  • pullDeltaOnBeforeAccess: When true, your handler will be called before each access to the data source and the GUI will block until the handler returns

  • pullDeltaOnAfterWrite: When true, your handler will be called after each write to the data source and the GUI will block until the handler returns

And an extra option is available to allow event grouping: pullDeltaOnBeforeAccessDelay

This value in milliseconds will add a delay to all read requests that are sent to your agent. This delay will allow your agent to group multiple requests that are sent during this delay and call your handler only once.

You can set this value to 0 to disable this feature or find a good balance between the number of calls to the target API and an acceptable delay.

const myCustomDataSource = createReplicaDataSource({
  pullDeltaOnRestart: true, // Update cache each time the agent restarts.
  pullDeltaOnSchedule: '0 0 * * *', // Update cache each day at midnight.

  // Update cache each time data is accessed or written to from the GUI.
  pullDeltaOnBeforeAccess: true,
  pullDeltaOnAfterWrite: true,

  // Delay all read requests by 50ms to allow request grouping.
  pullDeltaOnBeforeAccessDelay: 50,

  // Handler to the records that changed from the API.
  pullDeltaHandler: async request => {
    // Implement handler here
  },
});

Programming your handler

To be able to fetch only the records that changed, you need to implement a pullDeltaHandler function.

To know which records may have changed: a state object is preserved between calls, and you are allowed to read from the cache.

Structure of the `request` object
{
  // This object is persisted between calls to the handler.
  // It is up to you to decide what to store in it so that you can detect changes.
  // It's value is:
  // - The last value you returned in the nextDeltaState field
  // - The value you provided on pullDumpHandler
  // - null otherwise
  previousDeltaState: { /* Any JSON serializable object */ },

  // When using `pullDeltaOnBeforeAccess` or `pullDeltaOnAfterWrite` flags, this
  // will contain the list of collections that are being accessed or written to.
  affectedCollections: ['...'],

  // Interface to read from the cache
  cache: { },

  // List of reasons why the handler was called
  reasons: [
    { name: 'startup' },
    { name: 'schedule' },
    { name: 'before-list', collection: '...', filter: ..., projection: ... },

  ]
}
Structure of the `response` object
{
  // If all changes could not be fetched in a single call, you can set the `more`
  // flag to true.
  // This will cause the handler to be called again immediately.
  more: true,

  // Value that should be persisted in the `previousDeltaState` field of the next
  // call to the handler.
  nextDeltaState: {/* Any JSON serializable object */},

  // List of records that were updated or created since the last call to the
  // handler.
  newOrUpdatedEntries: [
    { collection: 'posts', record: { id: 134, title: '...' } },
    { collection: 'comments', record: { id: 15554, content: '...' } },
    { collection: 'comments', record: { id: 15555, content: '...' } },
  ],

  // List of records that were deleted since the last call to the handler.
  // This list is used to remove the records from the cache, so providing the
  // full record is not necessary.
  deletedEntries: [
    { collection: 'posts', record: { id: 34 } },
    { collection: 'comments', record: { id: 554 } },
  ]
};

Example

const myCustomDataSource = createReplicaDataSource({
  // Update cache before each data access
  pullDeltaOnBeforeAccess: true,

  // Handler to the records that changed from the API.
  pullDeltaHandler: async request => {
    // Initialize variables
    const url = 'https://jsonplaceholder.typicode.com';
    const nextDeltaState = { ...request.previousDeltaState };
    const newOrUpdatedEntries = [];
    const deletedEntries = [];

    // Get records that changed on the target API
    for (const collection of request.affectedCollections) {
      // Extract last timestamp for this collection from previousDeltaState
      const mostRecentDate = request.previousDeltaState[collection];

      // Request API
      const filter = `or($gt(updatedAt,${timestamp}), $gt(deletedAt,${timestamp}))`;
      const response = await axios.get(`${url}/${collection}?filter=${filter}`);
      const entries = response.data.map(record => ({ collection, record }));

      // Append the new records to the lists of entries
      newOrUpdatedEntries.push(...entries.filter(entry => !entry.record.deletedAt));
      deletedEntries.push(...entries.filter(entry => entry.record.deletedAt));
    }

    return { more: false, nextDeltaState, newOrUpdatedEntries, deletedEntries };
  },
});