openvibers/openvibe.sdk Commits
openvibe-sdk 0.37.2: realtime callbacks get the event's opaque cursor (#31)
Changes
@@@ -3,6 +3,13 @@All notable changes to `openvibe-sdk`. The package follows semver; while it is `0.x`, a minorrelease may change an API and says so here.++## 0.37.2 (2026-10-10)++++- `openvibe-sdk/realtime`: each event's callback gets its opaque `cursor` (the SSE id Events sent) beside `seq`:++ `subscribe(topics, (event, { seq, cursor }) => …)`. Save the cursor after handling the event and pass it back as++ `lastEventId` to resume, so a subscriber never needs the numeric seq (plan T7). `cursor` is null when Events sent no++ id. Additive.++## 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@@@ -3,7 +3,7 @@"stage": "alpha","code": true,"wave": "W2",-- "version": "0.37.1",++ "version": "0.37.2","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",@@@ -1,4 +1,4 @@--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.37.1, MIT). Do not edit.++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.37.2, 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.1';++const SDK_VERSION = '0.37.2';module.exports = {createClient,@@@ -955,16 +955,17 @@ function (module, exports) {/*** openvibe-sdk/realtime: the browser realtime plane inside OpenVibe.Events (ADR-005), over SSE.*-- * const sub = subscribe(['live.stream.*'], (event, { seq }) => { … }, {++ * const sub = subscribe(['live.stream.*'], (event, { seq, cursor }) => { … save cursor … }, {* client, // or url: 'https://openvibe.events'-- * lastEventId: savedSeq, // resume after a reload++ * lastEventId: savedCursor, // resume after a reload (the `cursor` the callback was given)* onGap: (gap) => refetchState(),* });* sub.close();** Transport: EventSource when the environment has one and no Bearer token is needed (cookies ride* along with withCredentials); otherwise fetch with a streamed body (Node, or a token). Either-- * way it resumes from the last seq it saw (Last-Event-ID), skips anything it already delivered,++ * way it resumes from the last position it saw (Last-Event-ID: Events' opaque cursor, the `cursor` each++ * callback gets; an older Events sends a bare seq), skips anything it already delivered,* and reports an `event: gap` (events missed beyond retention or replay limits) through onGap.* Browser-safe.*/@@@ -1015,7 +1016,7 @@ function subscribe(topics, onEvent, opts = {}) {state.lastSeq = seq;}if (id !== undefined && id !== '') state.lastId = String(id);-- try { onEvent(msg && msg.event, { seq }); } catch (err) { report(err); }++ try { onEvent(msg && msg.event, { seq, cursor: id !== undefined && id !== '' ? String(id) : null }); } catch (err) { report(err); }}async function streamUrl() {@@@ -1,12 +1,12 @@{"name": "openvibe-sdk",-- "version": "0.37.1",++ "version": "0.37.2","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-sdk",-- "version": "0.37.1",++ "version": "0.37.2","license": "MIT","devDependencies": {"@electric-sql/pglite": "^0.5.8",@@@ -1,6 +1,6 @@{"name": "openvibe-sdk",-- "version": "0.37.1",++ "version": "0.37.2","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",@@@ -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.1';++const SDK_VERSION = '0.37.2';module.exports = {createClient,@@@ -2,16 +2,17 @@/*** openvibe-sdk/realtime: the browser realtime plane inside OpenVibe.Events (ADR-005), over SSE.*-- * const sub = subscribe(['live.stream.*'], (event, { seq }) => { … }, {++ * const sub = subscribe(['live.stream.*'], (event, { seq, cursor }) => { … save cursor … }, {* client, // or url: 'https://openvibe.events'-- * lastEventId: savedSeq, // resume after a reload++ * lastEventId: savedCursor, // resume after a reload (the `cursor` the callback was given)* onGap: (gap) => refetchState(),* });* sub.close();** Transport: EventSource when the environment has one and no Bearer token is needed (cookies ride* along with withCredentials); otherwise fetch with a streamed body (Node, or a token). Either-- * way it resumes from the last seq it saw (Last-Event-ID), skips anything it already delivered,++ * way it resumes from the last position it saw (Last-Event-ID: Events' opaque cursor, the `cursor` each++ * callback gets; an older Events sends a bare seq), skips anything it already delivered,* and reports an `event: gap` (events missed beyond retention or replay limits) through onGap.* Browser-safe.*/@@@ -62,7 +63,7 @@ function subscribe(topics, onEvent, opts = {}) {state.lastSeq = seq;}if (id !== undefined && id !== '') state.lastId = String(id);-- try { onEvent(msg && msg.event, { seq }); } catch (err) { report(err); }++ try { onEvent(msg && msg.event, { seq, cursor: id !== undefined && id !== '' ? String(id) : null }); } catch (err) { report(err); }}async function streamUrl() {@@@ -75,9 +75,11 @@ run([else { res.write(cframe(2)); res.write(cframe(3)); }});const got = [];-- const sub = subscribe('live.*', (event, { seq }) => got.push([seq, event.event_id]), { url: srv.url, token: 't', reconnectDelayMs: 10 });++ const cursors = [];++ const sub = subscribe('live.*', (event, { seq, cursor }) => { got.push([seq, event.event_id]); cursors.push(cursor); }, { url: srv.url, token: 't', reconnectDelayMs: 10 });await waitFor(() => got.length === 3);assert.deepEqual(got, [[1, 'e1'], [2, 'e2'], [3, 'e3']], 'the replayed event is not delivered twice');++ assert.deepEqual(cursors, [cur(1), cur(2), cur(3)], 'each callback gets its event\'s opaque cursor, to save and resume from');assert.equal(seen[1], cur(2), 'the reconnect resumes from the cursor');assert.equal(sub.lastEventId, cur(3));sub.close();
@@ -3,6 +3,13 @@
@@ -3,6 +3,13 @@
All notable changes to `openvibe-sdk`. The package follows semver; while it is `0.x`, a minor
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.
release may change an API and says so here.
+## 0.37.2 (2026-10-10)
+
+- `openvibe-sdk/realtime`: each event's callback gets its opaque `cursor` (the SSE id Events sent) beside `seq`:
+ `subscribe(topics, (event, { seq, cursor }) => …)`. Save the cursor after handling the event and pass it back as
+ `lastEventId` to resume, so a subscriber never needs the numeric seq (plan T7). `cursor` is null when Events sent no
+ id. Additive.
+
## 0.37.1 (2026-10-10)
## 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-sdk/testing`'s mock Events answers a pull with `latest_cursor` (the head as an opaque cursor), as
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
"stage": "alpha",
"stage": "alpha",
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
- "version": "0.37.1",
+ "version": "0.37.2",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"deployed": false,
"deployed": false,
"deployment": "none: a package released as git tags; services and apps pin a tag",
"deployment": "none: a package released as git tags; services and apps pin a tag",
@@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
-// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.37.1, MIT). Do not edit.
+// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.37.2, MIT). Do not edit.
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// 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.
// (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 */
/* eslint-disable */
@@ -42,7 +42,7 @@ const { paginate, offsetPager } = __load(7);
@@ -42,7 +42,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.37.1';
+const SDK_VERSION = '0.37.2';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -955,16 +955,17 @@ function (module, exports) {
@@ -955,16 +955,17 @@ function (module, exports) {
/**
/**
* openvibe-sdk/realtime: the browser realtime plane inside OpenVibe.Events (ADR-005), over SSE.
* openvibe-sdk/realtime: the browser realtime plane inside OpenVibe.Events (ADR-005), over SSE.
*
*
- * const sub = subscribe(['live.stream.*'], (event, { seq }) => { … }, {
+ * const sub = subscribe(['live.stream.*'], (event, { seq, cursor }) => { … save cursor … }, {
* client, // or url: 'https://openvibe.events'
* client, // or url: 'https://openvibe.events'
- * lastEventId: savedSeq, // resume after a reload
+ * lastEventId: savedCursor, // resume after a reload (the `cursor` the callback was given)
* onGap: (gap) => refetchState(),
* onGap: (gap) => refetchState(),
* });
* });
* sub.close();
* sub.close();
*
*
* Transport: EventSource when the environment has one and no Bearer token is needed (cookies ride
* Transport: EventSource when the environment has one and no Bearer token is needed (cookies ride
* along with withCredentials); otherwise fetch with a streamed body (Node, or a token). Either
* along with withCredentials); otherwise fetch with a streamed body (Node, or a token). Either
- * way it resumes from the last seq it saw (Last-Event-ID), skips anything it already delivered,
+ * way it resumes from the last position it saw (Last-Event-ID: Events' opaque cursor, the `cursor` each
+ * callback gets; an older Events sends a bare seq), skips anything it already delivered,
* and reports an `event: gap` (events missed beyond retention or replay limits) through onGap.
* and reports an `event: gap` (events missed beyond retention or replay limits) through onGap.
* Browser-safe.
* Browser-safe.
*/
*/
@@ -1015,7 +1016,7 @@ function subscribe(topics, onEvent, opts = {}) {
@@ -1015,7 +1016,7 @@ function subscribe(topics, onEvent, opts = {}) {
state.lastSeq = seq;
state.lastSeq = seq;
}
}
if (id !== undefined && id !== '') state.lastId = String(id);
if (id !== undefined && id !== '') state.lastId = String(id);
- try { onEvent(msg && msg.event, { seq }); } catch (err) { report(err); }
+ try { onEvent(msg && msg.event, { seq, cursor: id !== undefined && id !== '' ? String(id) : null }); } catch (err) { report(err); }
}
}
async function streamUrl() {
async function streamUrl() {
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.37.1",
+ "version": "0.37.2",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.37.1",
+ "version": "0.37.2",
"license": "MIT",
"license": "MIT",
"devDependencies": {
"devDependencies": {
"@electric-sql/pglite": "^0.5.8",
"@electric-sql/pglite": "^0.5.8",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.37.1",
+ "version": "0.37.2",
"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.",
"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",
"main": "index.js",
"types": "types/index.d.ts",
"types": "types/index.d.ts",
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.37.1';
+const SDK_VERSION = '0.37.2';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -2,16 +2,17 @@
@@ -2,16 +2,17 @@
/**
/**
* openvibe-sdk/realtime: the browser realtime plane inside OpenVibe.Events (ADR-005), over SSE.
* openvibe-sdk/realtime: the browser realtime plane inside OpenVibe.Events (ADR-005), over SSE.
*
*
- * const sub = subscribe(['live.stream.*'], (event, { seq }) => { … }, {
+ * const sub = subscribe(['live.stream.*'], (event, { seq, cursor }) => { … save cursor … }, {
* client, // or url: 'https://openvibe.events'
* client, // or url: 'https://openvibe.events'
- * lastEventId: savedSeq, // resume after a reload
+ * lastEventId: savedCursor, // resume after a reload (the `cursor` the callback was given)
* onGap: (gap) => refetchState(),
* onGap: (gap) => refetchState(),
* });
* });
* sub.close();
* sub.close();
*
*
* Transport: EventSource when the environment has one and no Bearer token is needed (cookies ride
* Transport: EventSource when the environment has one and no Bearer token is needed (cookies ride
* along with withCredentials); otherwise fetch with a streamed body (Node, or a token). Either
* along with withCredentials); otherwise fetch with a streamed body (Node, or a token). Either
- * way it resumes from the last seq it saw (Last-Event-ID), skips anything it already delivered,
+ * way it resumes from the last position it saw (Last-Event-ID: Events' opaque cursor, the `cursor` each
+ * callback gets; an older Events sends a bare seq), skips anything it already delivered,
* and reports an `event: gap` (events missed beyond retention or replay limits) through onGap.
* and reports an `event: gap` (events missed beyond retention or replay limits) through onGap.
* Browser-safe.
* Browser-safe.
*/
*/
@@ -62,7 +63,7 @@ function subscribe(topics, onEvent, opts = {}) {
@@ -62,7 +63,7 @@ function subscribe(topics, onEvent, opts = {}) {
state.lastSeq = seq;
state.lastSeq = seq;
}
}
if (id !== undefined && id !== '') state.lastId = String(id);
if (id !== undefined && id !== '') state.lastId = String(id);
- try { onEvent(msg && msg.event, { seq }); } catch (err) { report(err); }
+ try { onEvent(msg && msg.event, { seq, cursor: id !== undefined && id !== '' ? String(id) : null }); } catch (err) { report(err); }
}
}
async function streamUrl() {
async function streamUrl() {
@@ -75,9 +75,11 @@ run([
@@ -75,9 +75,11 @@ run([
else { res.write(cframe(2)); res.write(cframe(3)); }
else { res.write(cframe(2)); res.write(cframe(3)); }
});
});
const got = [];
const got = [];
- const sub = subscribe('live.*', (event, { seq }) => got.push([seq, event.event_id]), { url: srv.url, token: 't', reconnectDelayMs: 10 });
+ const cursors = [];
+ const sub = subscribe('live.*', (event, { seq, cursor }) => { got.push([seq, event.event_id]); cursors.push(cursor); }, { url: srv.url, token: 't', reconnectDelayMs: 10 });
await waitFor(() => got.length === 3);
await waitFor(() => got.length === 3);
assert.deepEqual(got, [[1, 'e1'], [2, 'e2'], [3, 'e3']], 'the replayed event is not delivered twice');
assert.deepEqual(got, [[1, 'e1'], [2, 'e2'], [3, 'e3']], 'the replayed event is not delivered twice');
+ assert.deepEqual(cursors, [cur(1), cur(2), cur(3)], 'each callback gets its event\'s opaque cursor, to save and resume from');
assert.equal(seen[1], cur(2), 'the reconnect resumes from the cursor');
assert.equal(seen[1], cur(2), 'the reconnect resumes from the cursor');
assert.equal(sub.lastEventId, cur(3));
assert.equal(sub.lastEventId, cur(3));
sub.close();
sub.close();