openvibers/openvibe.sdk Commits

openvibe-sdk 0.37.1: the mock Events' pull carries latest_cursor (#30)

OP OpenVibers committed 10 Oct 2026 · a4c5e469e7 · 9 files, +20 −11

* openvibe-sdk 0.37.1: the mock Events' pull carries latest_cursor

The SDK side of Events#45. `openvibe-sdk/testing`'s mock Events answers a pull with `latest_cursor` (the head as an opaque cursor), as OpenVibe.Events now does, and `events.pull()` documents it. Consumers that start at "now" (Community's relay worker first) can then store an opaque cursor from the start and never read the numeric `latest_seq`. That's plan T7: retire the global `seq` promise once every consumer uses cursors.

0.37.1, additive. `test/events.test.js` checks the mock's `latest_cursor`, and that nothing comes after it.

* 0.37.1: version constant, browser bundle, changelog heading and STATUS

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex c1774f3..43f672a 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -3,6 +3,12 @@
All notable changes to `openvibe-sdk`. The package follows semver; while it is `0.x`, a minor
release may change an API and says so here.
++## 0.37.1 (2026-10-10)
++
++- `openvibe-sdk/testing`'s mock Events answers a pull with `latest_cursor` (the head as an opaque cursor), as
++ OpenVibe.Events does since Events#45. A consumer that starts at "now" stores it and reads on with `after=`,
++ never needing the numeric `latest_seq`. `events.pull()` documents it. Additive.
++
## 0.37.0 (2026-10-08)
- **`openvibe-sdk/account-data`: `startSubscriptions()`**, the boot-time half every adopter had copied. It creates any
ddiff --git a/STATUS.json b/STATUS.json
iindex a5e498e..e83b26a 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -3,7 +3,7 @@
"stage": "alpha",
"code": true,
"wave": "W2",
-- "version": "0.37.0",
++ "version": "0.37.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"deployed": false,
"deployment": "none: a package released as git tags; services and apps pin a tag",
ddiff --git a/browser/openvibe-sdk.mjs b/browser/openvibe-sdk.mjs
iindex 0005c66..71dfc2e 100644
---- a/browser/openvibe-sdk.mjs
++++ b/browser/openvibe-sdk.mjs
@@@ -1,4 +1,4 @@
--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.37.0, MIT). Do not edit.
++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.37.1, MIT). Do not edit.
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// (auth, registry, modules, realtime, media, community, bot, search, chat, ai, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
/* eslint-disable */
@@@ -42,7 +42,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
--const SDK_VERSION = '0.37.0';
++const SDK_VERSION = '0.37.1';
module.exports = {
createClient,
ddiff --git a/package-lock.json b/package-lock.json
iindex 836de2e..1effa93 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-sdk",
-- "version": "0.37.0",
++ "version": "0.37.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-sdk",
-- "version": "0.37.0",
++ "version": "0.37.1",
"license": "MIT",
"devDependencies": {
"@electric-sql/pglite": "^0.5.8",
ddiff --git a/package.json b/package.json
iindex 01d38c3..33682d0 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-sdk",
-- "version": "0.37.0",
++ "version": "0.37.1",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"main": "index.js",
"types": "types/index.d.ts",
ddiff --git a/src/core/index.js b/src/core/index.js
iindex c06364a..8760337 100644
---- a/src/core/index.js
++++ b/src/core/index.js
@@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
--const SDK_VERSION = '0.37.0';
++const SDK_VERSION = '0.37.1';
module.exports = {
createClient,
ddiff --git a/src/events.js b/src/events.js
iindex 310aa2c..7658b43 100644
---- a/src/events.js
++++ b/src/events.js
@@@ -182,9 +182,10 @@ function createEventsClient(client, { source, baseUrl } = {}) {
}
/**
-- * One page: { events: [{ seq, cursor, event }], next_cursor, next_after_seq, latest_seq, gap? }.
-- * Pass `after` — a page's opaque `next_cursor` — to resume exactly where the previous page ended;
-- * `afterSeq`, the numeric position, remains for one release.
++ * One page: { events: [{ seq, cursor, event }], next_cursor, next_after_seq, latest_seq, latest_cursor, gap? }.
++ * Pass `after` — a page's opaque `next_cursor` — to resume exactly where the previous page ended, or
++ * `latest_cursor` to start at the head without history; `afterSeq`, the numeric position, remains for
++ * one release.
*/
function pull({ topic = '*', after, afterSeq = 0, limit } = {}) {
return call({ path: '/api/v1/events', query: {
ddiff --git a/src/testing/index.js b/src/testing/index.js
iindex 6074f92..efec62a 100644
---- a/src/testing/index.js
++++ b/src/testing/index.js
@@@ -480,7 +480,7 @@ function createMockPlatform(opts = {}) {
if (pats.some(([x, re]) => re.test(e.event.event_type) && readable(who.principal, e, x))) out.push({ seq: e.seq, cursor: eventCursor(e.seq), event: e.event });
}
if (out.length < limit) cursor = Math.max(cursor, lastSeq);
-- return json(200, { ...page, events: out, next_after_seq: cursor, next_cursor: eventCursor(cursor), latest_seq: lastSeq });
++ return json(200, { ...page, events: out, next_after_seq: cursor, next_cursor: eventCursor(cursor), latest_seq: lastSeq, latest_cursor: eventCursor(lastSeq) });
}
if ((r = path.match(/^\/api\/v1\/events\/([^/]+)$/)) && req.method === 'GET') {
const who = eventsPrincipal(req, 'events.event.read', 'events.app.read');
ddiff --git a/test/events.test.js b/test/events.test.js
iindex 8819d23..93085f9 100644
---- a/test/events.test.js
++++ b/test/events.test.js
@@@ -72,6 +72,8 @@ run([
assert.equal(page.next_after_seq, 3);
assert.equal(page.latest_seq, 5);
assert.match(page.next_cursor, /^c1\./, 'the page carries an opaque next_cursor');
++ assert.match(page.latest_cursor, /^c1\./, 'and the head as an opaque latest_cursor');
++ assert.deepStrictEqual((await events.pull({ topic: 'media.vod.*', after: page.latest_cursor })).events, [], 'nothing after the head');
assert.match(page.events[0].cursor, /^c1\./, 'each event carries its cursor');
// The opaque cursor round-trips: `after` resumes exactly where the page ended.
const rest = await events.pull({ topic: 'media.vod.*', after: page.next_cursor, limit: 2 });