Links:
GIPOD LDES:
https://private-api.gipod.vlaanderen.be/api/v1/ldes/mobility-hindrances
LDES specification →
https://w3id.org/ldes/specification
GIPOD LDES API documentation (search for Ldes) →
https://private-api.gipod.vlaanderen.be/swagger/index.html
LDES client →
https://github.com/TREEcg/event-stream-client/tree/main/packages/actor-init-ldes-client
the list of the taxonomy for the different Hindrances Consequences.
https://private-api.gipod.vlaanderen.be/api/v1/taxonomies/mobility-hindrance/consequencetypes
What does a mobility hindrance look like?
The mobility hindrance data model is part of a Flemish data standard, called Inname Openbaar Domein (scroll to Mobiliteitshinder, to discover the properties of a mobility hindrance). GIPOD used this model as their base model, but extended it to their needs (implementation model).
According to the LDES specification, each LDES should link to or include a Shapes Constaint Language (SHACL) shape. A SHACL shape can be defined as a technical contract to describe that shape of data.
The LDES of mobility hindrances
The LDES accepts a query string parameter, called generatedAtTime, allowing users to specify a datetime, a point in time, indicating how far back in time they want to go to synchronize. The query string parameter is also part of the mobility hindrance data model, and indicates the time the event was inserted in the database.
Furthermore, as there are too many mobility hindrance objects to display on 1 page, we applied a time-based fragmentation, that resulted in fragments containing 250 mobility hindrance objects and links to previous and next pages. This time-based fragmentation means that the oldest mobility hindrance objects (the ones with the earliest generatedAtTime values), will be on the first fragment, while the most recent mobility hindrance objects will be on the latest fragment. So, entering the LDES URL without a query string in the browser or the LDES client, you will be redirected to the fragment containing the oldest mobility hindrance objects (objects with the earliest generatedAtTime values). Passing the current time as value of the generatedAtTime query string will redirect you to the latest fragment (containing the most recent mobility hindrace objects, the ones with the latest generatedAtTime values).
How to consume the mobility hindrance LDES?
In order to consume an LDES, there is an LDES client needed. We will explain how the hindrance objects can be consumed as JSON objects with Javascript.
We created a demo script that shows how to parse the Gipod ID, description, time schedule, time period, status, WKT geometry and consequences.
First, create a ‘test.js’ file with following content:
const fs = require('fs'); const RRule = require('rrule').RRule; const RRuleset = require('rrule').RRuleSet; const pxd = require('parse-xsd-duration'); const context = JSON.parse(fs.readFileSync('./context.jsonld').toString()); const newEngine = require('@treecg/actor-init-ldes-client').newEngine; let eventstreamSync; // LDES client const FOLDER_OF_STATE = `./.ldes`; const LOCATION_OF_STATE = `./.ldes/state.json`; // When the consequences of a hindrance zone matches one of below list, the hindrance is returned // Otherwise the hindrance is filtered let interestedConsequences = [ "api/v1/taxonomies/mobility-hindrance/consequencetypes/e4ea1344-aa27-40e8-b5af-94ec5f7956f8", "api/v1/taxonomies/mobility-hindrance/consequencetypes/8eda1611-902b-4c9a-8b3c-4c23a49d7c5d", "api/v1/taxonomies/mobility-hindrance/consequencetypes/3c9d3c6e-c5bf-477b-a102-d12654ce5ef0", "api/v1/taxonomies/mobility-hindrance/consequencetypes/6a71c816-511f-490c-9248-c68ded67ecd9", "api/v1/taxonomies/mobility-hindrance/consequencetypes/c53813ab-814f-4ff4-8a87-6934c72e175f", "api/v1/taxonomies/mobility-hindrance/consequencetypes/4981fd46-9536-415b-bd30-e53c0cedd799", "api/v1/taxonomies/mobility-hindrance/consequencetypes/6dd14722-79ad-4d25-aa0e-91e7fc53877b", "api/v1/taxonomies/mobility-hindrance/consequencetypes/23c9463a-c199-4db3-a8ce-40a088066cb3", "api/v1/taxonomies/mobility-hindrance/consequencetypes/cd1bbb8c-503f-4968-8483-01a4c2092d51", "api/v1/taxonomies/mobility-hindrance/consequencetypes/5f8ff25a-87c7-47c8-9332-35d4fabf4b07", "api/v1/taxonomies/mobility-hindrance/consequencetypes/7cbd0430-f6d4-4c74-8b8e-21b677e6b3d7", "api/v1/taxonomies/mobility-hindrance/consequencetypes/10c5101d-31fb-4909-a022-d76f868f7f50", "api/v1/taxonomies/mobility-hindrance/consequencetypes/ee31fd67-b75e-4499-9ad4-0a595717a9c7", "api/v1/taxonomies/mobility-hindrance/consequencetypes/5e5a1a0b-eaab-4b98-a5c8-6a4664cdb909", "api/v1/taxonomies/mobility-hindrance/consequencetypes/e52587c7-5566-4c1c-889c-7fcc947e4c4b", "api/v1/taxonomies/mobility-hindrance/consequencetypes/b922f580-68d3-471d-8712-8175417ad769", "api/v1/taxonomies/mobility-hindrance/consequencetypes/427bc6a6-619c-482d-934c-bda986df18d1" ]; // If run takes longer than 50 minutes, pause the LDES Client const TIMEOUT = 3000000; // milliseconds // After pausing, the state will be written to LOCATION_OF_STATE // When restarting, the state will be read from LOCATION_OF_STATE setTimeout(() => { console.log("Timeout - Pausing the LDES client to save state."); eventstreamSync.pause(); }, TIMEOUT); // Pause when exiting with CTRL+C // process.on('SIGTERM', function() { // console.log("Caught interrupt signal. Pausing the LDES client to save state."); // eventstreamSync.pause(); // }); start(); async function start() { await harvest(); } function harvest() { return new Promise((resolve, reject) => { try { let url = "https://private-api.gipod.beta-vlaanderen.be/api/v1/ldes/mobility-hindrances"; let options = { "representation": "Object", //Object or Quads "emitMemberOnce": true, "disableSynchronization": true, "jsonLdContext": context, "processedURIsCount": 15000 }; let LDESClient = new newEngine(); // Retrieve state const state = loadState(); if (state === null) { eventstreamSync = LDESClient.createReadStream(url, options); } else { eventstreamSync = LDESClient.createReadStream(url, options, state); } eventstreamSync.on('data', (member) => { const object = member.object; console.log("test: " + member.id) if (hindranceHasInterestingConsequence(member.object)) { if (object.gipodId && object.gipodId.value) { const gipodId = object.gipodId.value; console.log("Gipod ID: " + gipodId); } if (object.createdOn) { // Use this timestamp to update your database with the latest version (version materialisation) console.log("Created on: " + object.createdOn); } const description = object.description; console.log("Description: " + description); let hindranceStillActive = false; if (object.timeSchedule) { for (let schedule of object.timeSchedule) { console.log("Schedule: " + JSON.stringify(schedule)); const periodsFromSchedule = processSchedule(schedule); for (let p of periodsFromSchedule) { const start = p.start; const end = p.end; console.log("Time period: " + start + " - " + end); if (new Date().getTime() < new Date(end).getTime()) hindranceStillActive = true; } } } if (object.period) { for (let p of object.period) { const start = p.start; const end = p.end; console.log("Time period: " + start + " - " + end); if (new Date().getTime() < new Date(end).getTime()) hindranceStillActive = true; } } if (hindranceStillActive) { console.log("Hindrance is still active now or in the future"); } if (object.status && object.status.prefLabel) { console.log("Status: " + object.status.prefLabel); } for (let z of object.zone) { const geometry = z['geometry'].wkt; console.log("WKT geometry: " + geometry); if (z.consequence && Array.isArray(z.consequence)) { for (let con of z.consequence) { if (con.prefLabel) { console.log("Consequence: " + con.prefLabel); } } } else if (z.consequence) { if (z.consequence.prefLabel) { console.log("Consequence: " + z.consequence.prefLabel); } } } } resolve(); }); eventstreamSync.on('pause', () => { console.log("Paused!") // Export current state, but only when paused! let state = eventstreamSync.exportState(); saveState(state); }); eventstreamSync.on('end', () => { console.log("No more data!"); // Save state let state = eventstreamSync.exportState(); saveState(state); resolve(); }); } catch (e) { console.error(e); reject(e); } }); } function hindranceHasInterestingConsequence(object) { if (object.zone) { for (let z of object.zone) { if (z.consequence && Array.isArray(z.consequence)) { for (let con of z.consequence) { if (interestedConsequences.includes(con.id)) { return true; } } } else if (z.consequence) { if (interestedConsequences.includes(z.consequence.id)) { return true; } } } } return false; } function saveState(clientState) { if (!fs.existsSync(FOLDER_OF_STATE)) { fs.mkdirSync(FOLDER_OF_STATE, { recursive: true }); } fs.writeFileSync(`${LOCATION_OF_STATE}`, JSON.stringify(clientState)); } function loadState() { if (fs.existsSync(LOCATION_OF_STATE)) { return JSON.parse(fs.readFileSync(LOCATION_OF_STATE).toString()); } return null; } function processSchedule(schedule) { const frequency = schedule.repeatFrequency ? schedule.repeatFrequency : "unknown"; const repeatCount = schedule.repeatCount ? schedule.repeatCount : "unknown"; // Number of times it occurs. const startDate = schedule.startDate ? schedule.startDate : "unknown"; const endDate = schedule.endDate ? schedule.endDate : "unknown"; const exceptDateArray = schedule.exceptDate ? schedule.exceptDate : []; const byDay = schedule.byDay ? schedule.byDay : []; // Days of the month it takes place. const byMonthDay = schedule.byMonthDay ? schedule.byMonthDay : []; // Days of the month it takes place. const duration = schedule.duration ? schedule.duration : "unknown"; // How long an event specified by this schedule will last. const startTime = schedule.startTime ? schedule.startTime : "unknown"; let rule = { "interval": 1 }; //// Add frequency switch (frequency) { case 'P1W': rule["freq"] = RRule.WEEKLY; break; case 'P1D': rule["freq"] = RRule.DAILY; break; case 'P1Y': rule["freq"] = RRule.YEARLY; break; case 'P1M': rule["freq"] = RRule.MONTHLY; break; } //// Add count if (repeatCount != "unknown") rule["count"] = repeatCount; //// Add weekday let byWeekday = []; for (let day of byDay) { switch (day) { case 'http://schema.org/Monday': byWeekday.push(RRule.MO); break; case 'http://schema.org/Tuesday': byWeekday.push(RRule.TU); break; case 'http://schema.org/Wednesday': byWeekday.push(RRule.WE); break; case 'http://schema.org/Thursday': byWeekday.push(RRule.TH); break; case 'http://schema.org/Friday': byWeekday.push(RRule.FR); break; case 'http://schema.org/Saturday': byWeekday.push(RRule.SA); break; case 'http://schema.org/Sunday': byWeekday.push(RRule.SU); break; } } rule["byweekday"] = byWeekday; // Add monthday rule["bymonthday"] = byMonthDay; //// Add dtstart if (startDate != "unknown") rule["dtstart"] = new Date(startDate); //// Add until if (endDate != "unknown") rule["until"] = new Date(endDate); else rule["until"] = new Date(startDate.getTime() + 630720000*1000); // Use 20 years as end // Create a ruleset: let rruleSet = new RRuleset(); rruleSet.rrule(new RRule(rule)); // Add exception dates for (let exdate of exceptDateArray) { rruleSet.exdate(new Date(exdate)); } // Get all occurrence dates (Date instances): let occurences = rruleSet.all(); // Based on the duration and starting time we can calculate the period intervals const durationInSeconds = pxd.default(duration); let periods = []; for (let occurence of occurences) { const year = occurence.getFullYear(); let month = occurence.getMonth() + 1; if (month < 10) month = '0' + month; let day = occurence.getDate(); if (day < 10) day = '0' + day; const startOfOccurence = new Date(`${year}-${month}-${day}T${startTime}`); const endOfOccurence = new Date(startOfOccurence.getTime() + durationInSeconds*1000); periods.push({ "start": startOfOccurence.toISOString(), "end": endOfOccurence.toISOString() }) } return periods; }
Second, create a “package.json” file:
{ "name": "test", "version": "1.0.0", "description": "", "main": "test.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "@treecg/actor-init-ldes-client": "latest", "parse-xsd-duration": "^0.5.0", "rdf-data-factory": "^1.1.0", "rdf-dereference": "^2.0.0", "rdf-store-stream": "^1.3.0", "rrule": "^2.7.0" } }
Third, create a “context.jsonld” file:
{ "@context": [{ "@base": "https://private-api.gipod.beta-vlaanderen.be", "id": "@id", "value": "@value", "type": "@type", "@language": "nl-BE", "xsd": "http://www.w3.org/2001/XMLSchema#", "dct": "http://purl.org/dc/terms/", "dc": "http://purl.org/dc/elements/1.1", "PublicDomainOccupancy": "https://data.vlaanderen.be/ns/mobiliteit#Inname", "Groundwork": "https://data.vlaanderen.be/ns/mobiliteit#Grondwerk", "Work": "https://data.vlaanderen.be/ns/mobiliteit#Werk", "Event": "https://data.vlaanderen.be/ns/mobiliteit#Evenement", "MobilityHindrance": "https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder", "SignalingPermit": "https://data.vlaanderen.be/ns/mobiliteit#Signalisatievergunning", "TrenchSynergyRequest": "https://data.vlaanderen.be/ns/mobiliteit#Synergieaanvraag", "TrenchSynergy": "https://data.vlaanderen.be/ns/mobiliteit#Synergie", "Organisation": "http://www.w3.org/ns/org#Organization", "Identifier": "http://www.w3.org/ns/adms#Identifier", "ContactOrganisationInRole": "http://www.w3.org/ns/org#Organization", "ContactInfoPublic": "https://data.vlaanderen.be/ns/mobiliteit#ContactinfoPubliek", "AddressRepresentation": "http://www.w3.org/ns/locn#Address", "Geometry": "http://www.w3.org/ns/locn#Geometry", "Period": "http://data.europa.eu/m8g/PeriodOfTime", "PeriodWithDuration": "https://data.vlaanderen.be/ns/mobiliteit#PeriodeMetDuur", "TimeSchedule": "https://schema.org/Schedule", "Zone": "https://data.vlaanderen.be/ns/mobiliteit#Zone", "identifier": { "@id": "http://www.w3.org/ns/adms#identifier", "@type": "@id", "@container": "@set" }, "Identifier.identifier": { "@id": "http://www.w3.org/2004/02/skos/core#notation" }, "assignedByName": { "@id": "http://www.w3.org/ns/adms#schemaAgency" }, "address": { "@id": "http://www.w3.org/ns/locn#address", "@type": "@id" }, "isPublic": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Inname.openbaarDomein" }, "owner": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#beheerder", "@type": "@id" }, "description": { "@id": "http://purl.org/dc/terms/description", "@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" }, "duration": { "@id": "https://data.vlaanderen.be/ns/generiek#Tijdsschema.duur", "@type": "xsd:duration" }, "contactname": { "@id": "http://xmlns.com/foaf/0.1/name" }, "contactOrganisation": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#contactOrganisatie", "@type": "@id", "@container": "@set" }, "telephone": { "@id": "http://schema.org/telephone", "@container": "@set" }, "preferredName": { "@id": "http://www.w3.org/2004/02/skos/core#prefLabel", "@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" }, "category": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Grondwerk.categorie", "@type": "@id" }, "specification": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#specificatie", "@type": "@id" }, "isRelocationGroundwork": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Grondwerk.verplaatsingswerk", "@type": "xsd:boolean" }, "causingGroundwork": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#heeftOorzaakGrondwerk", "@type": "@id", "@container": "@set" }, "period": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#periode", "@type": "@id", "@container": "@set" }, "start": { "@id": "http://data.europa.eu/m8g/startTime", "@type": "xsd:dateTime" }, "end": { "@id": "http://data.europa.eu/m8g/endTime", "@type": "xsd:dateTime" }, "periodDuration": { "@id": "http://schema.org/duration", "@type": "xsd:duration" }, "timeSchedule": { "@id": "http://schema.org/eventSchedule", "@type": "@id", "@container": "@set" }, "startDate": { "@id": "http://schema.org/startDate", "@type": "xsd:date" }, "startTime": { "@id": "http://schema.org/startTime", "@type": "xsd:time" }, "endDate": { "@id": "http://schema.org/endDate", "@type": "xsd:date" }, "endTime": { "@id": "http://schema.org/endTime", "@type": "xsd:time" }, "consequence": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#gevolg", "@type": "@id" }, "status": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Inname.status", "@type": "@id" }, "publicDomainOccupancyType": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Inname.type", "@type": "@id", "@container": "@set" }, "hasConsequence": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Inname.heeftGevolg", "@type": "@id" }, "estimatedDuration": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#geschatteDuur", "@type": "xsd:duration" }, "zoneType": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#Zone.type", "@type": "@id", "@container": "@set" }, "byDay": { "@id": "http://schema.org/byDay" }, "byMonth": { "@id": "http://schema.org/byMonth", "@type": "xsd:integer" }, "byMonthDay": { "@id": "http://schema.org/byMonthDay", "@type": "xsd:integer", "@container": "@set" }, "repeatCount": { "@id": "http://schema.org/repeatCount", "@type": "xsd:integer" }, "repeatFrequency": { "@id": "http://schema.org/repeatFrequency" }, "exceptDate": { "@id": "http://schema.org/exceptDate", "@type": "xsd:date", "@container": "@set" }, "bySetPos": { "@id": "https://data.vlaanderen.be/ns/generiek#perSetpositie", "@type": "xsd:integer", "@container": "@set" }, "geometry": { "@id": "http://www.w3.org/ns/locn#geometry", "@type": "@id" }, "gipodId": { "@id": "https://gipod.vlaanderen.be/ns/gipod#gipodId" }, "isConsequenceOf": { "@reverse": "https://data.vlaanderen.be/ns/mobiliteit#Inname.heeftGevolg", "@type": "@id" }, "permittedBy": { "@id": "https://gipod.vlaanderen.be/ns/gipod#isVergundDoor", "@type": "@id" }, "prefLabel": { "@id": "http://www.w3.org/2004/02/skos/core#prefLabel" }, "reference": { "@id": "https://gipod.vlaanderen.be/ns/gipod#reference" }, "remark": { "@id": "https://gipod.vlaanderen.be/ns/gipod#remark", "@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" }, "addressRepresentation": { "@id": "https://www.w3.org/ns/locn#address", "@type": "@id" }, "wkt": { "@id": "http://www.opengis.net/ont/geosparql#asWKT", "@type": "http://www.opengis.net/ont/geosparql#wktLiteral" }, "zone": { "@id": "https://data.vlaanderen.be/ns/mobiliteit#zone", "@type": "@id", "@container": "@set" }, "lastModifiedOn": { "@id": "dct:modified", "@type": "xsd:dateTime" }, "lastModifiedBy": { "@id": "http://purl.org/dc/elements/1.1/contributor", "@type": "@id" }, "createdOn": { "@id": "dct:created", "@type": "xsd:dateTime" }, "createdBy": { "@id": "http://purl.org/dc/elements/1.1/creator", "@type": "@id" } }, { "adms": "http://www.w3.org/ns/adms#", "prov": "http://www.w3.org/ns/prov#", "ldes": "https://w3id.org/ldes#", "tree": "https://w3id.org/tree#", "eventName": "adms:versionNotes", "EventStream": "ldes:EventStream", "Node": "tree:Node", "view": "tree:view", "viewOf": { "@reverse": "view", "@type": "@id" }, "member": "tree:member", "relation": "tree:relation", "memberOf": { "@reverse": "member", "@type": "@id" }, "timestampPath": { "@id": "ldes:timestampPath", "@type": "@id" }, "versionOfPath": { "@id": "ldes:versionOfPath", "@type": "@id" }, "shape": { "@id": "tree:shape", "@type": "@id" }, "tree:node": { "@type": "@id" }, "tree:path": { "@type": "@id" }, "tree:value": { "@type": "xsd:dateTime" }, "generatedAtTime": { "@id": "prov:generatedAtTime", "@type": "xsd:dateTime" }, "isVersionOf": { "@id": "dct:isVersionOf", "@type": "@id" }, "items": "@included", "collectionInfo": "@included" }] }
And install the packages with following command in the terminal:
npm install
Now we can run the script as follows:
node --max-old-space-size=8192 test.js
The script currently uses a test LDES with one hindrance object.
The outputted JSON object, which you will process further, looks like this:
{ "@context": "https://private-api.gipod.test-vlaanderen.be/api/v1/context/gipod.jsonld", "id": "api/v1/mobility-hindrances/10031313/3", "type": "MobilityHindrance", "lastModifiedBy": { "id": "api/v1/organisations/6b023897-3708-08c7-bd41-fd107d57e0ce", "type": "Organisation", "preferredName": "Groen" }, "createdBy": "api/v1/organisations/6b023897-3708-08c7-bd41-fd107d57e0ce", "createdOn": "2020-12-04T16:06:41.32Z", "description": "my description", "isVersionOf": "api/v1/mobility-hindrances/10031313", "lastModifiedOn": "2020-12-04T16:07:15.69Z", "identifier": [{ "type": "Identifier", "Identifier.identifier": { "type": "gipodId", "value": "10031313" }, "assignedByName": "https://gipod.vlaanderen.be" }], "eventName": "MobilityHindranceWasImportedFromLegacy", "generatedAtTime": "2020-12-21T15:15:59.817Z", "status": { "id": "api/v1/taxonomies/statuses/a411c53e-db33-436a-9bb9-d62d535b661d", "prefLabel": "Onbekend" }, "owner": "api/v1/organisations/6b023897-3708-08c7-bd41-fd107d57e0ce", "period": [{ "type": "Period", "end": "2020-12-06T00:00:00Z", "start": "2020-12-05T00:00:00Z" }], "zone": [{ "id": "api/v1/mobility-hindrances/10031313/zones/59b7b880-f25a-4d18-bf8e-b25654a9287b", "type": "Zone", "geometry": { "type": "Geometry", "wkt": "POLYGON ((105277.37359766 191627.39540625, 103741.37359766 197387.39540625, 99901.37359766 193547.39540625, 100797.37359766 189707.39540625, 105277.37359766 191627.39540625))" }, "zoneType": [{ "id": "api/v1/taxonomies/zonetypes/0fb72ef7-6ac9-4a70-b295-a30ea215d250", "prefLabel": "HinderZone" }], "consequence": { "id": "api/v1/taxonomies/mobility-hindrance/consequencetypes/c53813ab-814f-4ff4-8a87-6934c72e175f", "prefLabel": "Geen doorgang voor gemotoriseerd verkeer" } }], "gipodId": { "type": "xsd:integer", "value": "10031313" } }
The script shows how to parse the WKT geometry, time period, schedule etc.
The function hindranceHasInterestingConsequence
demonstrates how objects can be filtered on consequence type.
The result of the script is:
Gipod ID: 10031313 Description: my description Time period: 2020-12-05T00:00:00Z - 2020-12-06T00:00:00Z Status: Onbekend WKT geometry: POLYGON ((105277.37359766 191627.39540625, 103741.37359766 197387.39540625, 99901.37359766 193547.39540625, 100797.37359766 189707.39540625, 105277.37359766 191627.39 540625)) Consequence: Geen doorgang voor gemotoriseerd verkeer
Restarting
In the script test.js, we also pause the stream after 50 minutes. This can be configured with the TIMEOUT property:
const TIMEOUT = 3000000; // milliseconds
setTimeout(() => eventstreamSync.pause(), TIMEOUT);
When pausing, this triggers the function “saveState”, which will save the state to the .ldes/state.json file.
When resuming, this state will be picked up again.
If you want to reset the client, remove the state file.
Add Comment