openvibers/openvibe.sdk Commits
Add the Pics gallery, uploads and albums to the SDK (#169)
Changes
@@@ -1,5 +1,9 @@# Changelog++## 0.126.0 (2026-11-09)++++- Add `openvibe-sdk/pics`: the public gallery and picture reads, private uploads with resumable chunks, publication, likes and albums on OpenVibe.Pics.++## 0.125.0 (2026-11-09)- openvibe-sdk/blog: posts are written and read as Markdown or as blocks (publishing.block@1), ?format=blocks@@@ -10,7 +10,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags> Supported browser and server clients for the OpenVibe platform.--**Status:** alpha, v0.125.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.++**Status:** alpha, v0.126.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30.**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.@@@ -19,7 +19,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tagsAfter v0.97.1 is tagged:```bash--npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.125.0++npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.126.0```It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).@@@ -693,3 +693,20 @@ await exchange.arena.vote(round.id, 'left');Camera choices can be read or saved on behalf of a signed-in person with `ov.cam.settings.get({ token })` and`ov.cam.settings.put({ token, values: { facing, lens, mic, resolution, mirror } })`. Server callers should forwardthe person's Network token; Cam keeps these preferences per person, and a missing device id is only a hint.++++### Pictures++++```js++const { createPicsClient } = require('openvibe-sdk/pics');++const pics = createPicsClient(client);++const gallery = await pics.gallery({ sort: 'new', tag: 'landscape' });++const session = await pics.uploads.create({ size: bytes.length, content_type: 'image/png', name: 'photo.png' });++// Send at most 1 MiB per chunk; read uploads.get(id).offset after an interrupted request.++await pics.uploads.chunk(session.id, 0, bytes);++const picture = await pics.uploads.complete(session.id); // private++await pics.publish(picture.slug, { listed: true, tags: ['landscape'] });++```++++`pics.albums` lists, creates and manages albums; `pics.like(slug, false)` removes a++like. Upload and write calls require a person's Network token. Gallery reads are++public. Resumable completion and likes are idempotent; creation is not retried.@@@ -3,17 +3,18 @@"stage": "alpha","code": true,"wave": "W2",-- "version": "0.125.0",-- "plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",++ "version": "0.126.0",++ "plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), \u00a73.2; roadmap \u00a730","deployed": false,"deployment": "none: a package released as git tags; services and apps pin a tag","contracts": "openvibe-contracts v0.338.0","features": ["menu (openvibe-sdk/menu): menuCard() builds one validated frame.menu-card@1 (plain text, the contract caps, https links on the site own origin or an openvibe.* host, an optional item picture) and menuRoute() is the Express/node:http GET /.well-known/openvibe-menu.json handler (public cache, CORS *, [] and one log line when the cards cannot be built)","cam (openvibe-sdk/cam): defineOvCamera({ src }) injects the <ov-camera> element from openvibe.cam once and resolves to its class, with typed attributes, events and methods (start/stop/capture/flip); browser-only, upload=\"off\" by default",++ "pics (openvibe-sdk/pics): the public gallery and one picture's reads, private uploads with resumable chunks, the owner's explicit publication, likes and albums on OpenVibe.Pics; reads are public, writes take a person's Network token","streams (openvibe-sdk/streams, S7): schedule(), listSchedules(), cancelSchedule() and remindSchedule() for scheduled public agent streams; postStep() and steps.post() from any harness; digest.read() and poll.open()/poll.result() for a Co-pilot service token; onControl() for pause, curtain, mute and end","reports (openvibe-sdk/reports, S7): write() a session report with the person's own token, private by default and idempotent per source and task ref, and read() one the caller may see; examples/cli-agent-stream.js streams any CLI command's run with steps",-- "chat-apps (openvibe-sdk/chat-apps): the three chat apps (Telegram, Discord, Slack) in one place — the delivery checks (Telegram's secret token, Slack's v0 signing, Discord's Ed25519), the button callback (signed once, used once, expiring), each app's payload as the same shape and splitText() for a long answer",++ "chat-apps (openvibe-sdk/chat-apps): the three chat apps (Telegram, Discord, Slack) in one place \u2014 the delivery checks (Telegram's secret token, Slack's v0 signing, Discord's Ed25519), the button callback (signed once, used once, expiring), each app's payload as the same shape and splitText() for a long answer","exchange (openvibe-sdk/exchange): the buyer side (chat, models, offers, presets, usage, receipts) and the seller side (offer, endpointOffers, removeEndpointOffer, earnings, verification) of OpenVibe.Exchange, with the platform webhook signature check; an offer is a Link machine's Network report or a standalone OpenAI-compatible endpoint registered on the public seller API","locker (openvibe-sdk/locker, openvibe-sdk/locker/server): the Locker envelope (locker.envelope@1) for end-to-end encryption, WebCrypto only, in Node and the browser: seal/open for text and bytes, sealStream/openStream, the three locks (people, password, link), person key pairs (ECDH P-256, ECDSA P-256), the eight-word fingerprint, wrapFor and rewrap, recoveryWords for the phrase to write down, opening a people lock by key, inspect for what an envelope says about itself, and the header checks a service runs without a key","the 1.0 core surface is frozen now, before 1.0 (core, auth, events, db, media): docs/one-point-oh.json gives every export its tier, test/surface.test.js pins the exact names, and removing or renaming one needs a major release; createCodeVerifier and pkceChallenge left in 0.86.0",@@@ -37,7 +38,7 @@"valkey, cache, queue, pubsub and a shared limits store on Valkey 9, each with an in-process twin for tests","commerce (openvibe-sdk/commerce): the shared Billing client for Tips and VIP, with intentKey and receipt","events: the PostgreSQL outbox (lease-claimed, several relays per table) and inbox (handler in its transaction)",-- "usage: createUsageReporter (openvibe-sdk/usage), the shared platform.usage-sample@1 reporter — validated readings queued idempotency-keyed in an outbox and relayed to Billing with retry, never dropped, refused readings kept",++ "usage: createUsageReporter (openvibe-sdk/usage), the shared platform.usage-sample@1 reporter \u2014 validated readings queued idempotency-keyed in an outbox and relayed to Billing with retry, never dropped, refused readings kept","the HTTP telemetry kit (openvibe-sdk/service): the platform.telemetry-sample@1 middleware aggregated per route|method|status_class per flush, with active-requests, p95 and event-loop-lag gauges, lifted from Network's server/telemetry.js and server/observability.js","the telemetry sink (openvibe-sdk/telemetry): telemetrySchema() DDL, createPgTelemetrySink() and pruneTelemetry(), a per-service PostgreSQL table every service stores platform.telemetry-sample@1 into","account-data (openvibe-sdk/account-data, ADR-033): telemetry_samples covered once by the SDK (exported as telemetry.json, anonymized on deletion), and uncovered() listing every table with a person column that no table map, task-keyed list or reasoned exemption covers",@@@ -46,8 +47,8 @@"the AI Exchange face of the one planner (openvibe-sdk/placement planAi, off by default): the hard gates (exact artifact, commercial right, privacy class, trust tier, capabilities, context, health, cost ceiling), the expected cost per success in integer nano-USD, sparse quality evidence marked and not trusted, and an explanation that never carries a secret or a host; the money math in openvibe-sdk/cost (nanoUsd, chargeTokens, expectedCostNano, costPerSuccessNano)","consent (openvibe-sdk/consent): the L24 check before any use of a person's content or likeness for AI or data; allowed() and filter() (batches of 500), onChange() drops a subject's answers on network.consent.changed, mock() for tests; fails closed, caches 60 s, server-side","openvibe-sdk/machines: the network's machines hub (list, get, catalogue, pair, rename, roles, revoke) against OpenVibe.Network, for a person and for the apps they consented to",-- "vibes (openvibe-sdk/vibes): Vibes Pay charges (create with the app reference as the Idempotency-Key, get, refund) and constant-time webhook verification (X-OpenVibe-Signature-V2, ±300 s) for developer apps",-- "streams (openvibe-sdk/streams): the Agent Streams plan S1 — start an agent stream on OpenVibe.Live (live.agent-stream@1) with its publish ticket, renew and revoke that ticket (the kill switch), poll state, post the last frame, set the curtain, switch visibility, end it and mint the view ticket; the publish ticket is a bearer of its own on the desk routes"++ "vibes (openvibe-sdk/vibes): Vibes Pay charges (create with the app reference as the Idempotency-Key, get, refund) and constant-time webhook verification (X-OpenVibe-Signature-V2, \u00b1300 s) for developer apps",++ "streams (openvibe-sdk/streams): the Agent Streams plan S1 \u2014 start an agent stream on OpenVibe.Live (live.agent-stream@1) with its publish ticket, renew and revoke that ticket (the kill switch), poll state, post the last frame, set the curtain, switch visibility, end it and mint the view ticket; the publish ticket is a bearer of its own on the desk routes"],"updated": "2026-11-09"}@@@ -1,4 +1,4 @@--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.125.0, MIT). Do not edit.++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.126.0, MIT). Do not edit.// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces// (auth, locker, registry, trust, modules, realtime, media, cam, mediaUpload, ui, community, homeFeed, bot, search, bench, meme, vision, themes, design, redact, chat, ai, credentials, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity./* eslint-disable */@@@ -56,7 +56,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.125.0';++const SDK_VERSION = '0.126.0';module.exports = {createClient,@@@ -282,6 +282,11 @@},"paginate": "function","parseTraceparent": "function",++ "pics": {++ "object": [++ "createPicsClient"++ ]++ },"play": {"object": ["createPlayClient"@@@ -930,6 +935,9 @@"openvibe-sdk/openre": {"createOpenReClient": "function"},++ "openvibe-sdk/pics": {++ "createPicsClient": "function"++ },"openvibe-sdk/placement": {"DEFAULT_P_FLOOR": "number","DEFAULT_TRUST": "array",@@@ -13,7 +13,8 @@ export const {projects, resources, vip, quest, live, inventory,tips, blog, reviews, wiki, coupons, deals,shoes, trade, space, host, news, food,-- actor, run, video, work, zone, play,-- download, openre, geo, usage, commerce, telemetry,++ actor, run, video, pics, work, zone,++ play, download, openre, geo, usage, commerce,++ telemetry,} = cjs;export default cjs;nnew file mode 100644@@@ -0,0 +1,7 @@++// Generated by scripts/esm.js from src/pics.js. Do not edit.++import cjs from '../src/pics.js';++++export const {++ createPicsClient,++} = cjs;++export default cjs;@@@ -60,6 +60,7 @@ module.exports = {actor: require('./src/actor'),run: require('./src/run'),video: require('./src/video'),++ pics: require('./src/pics'),work: require('./src/work'),zone: require('./src/zone'),play: require('./src/play'),@@@ -1,12 +1,12 @@{"name": "openvibe-sdk",-- "version": "0.125.0",++ "version": "0.126.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-sdk",-- "version": "0.125.0",++ "version": "0.126.0","license": "MIT","bin": {"openvibe-download": "bin/openvibe-download.js"@@@ -1,6 +1,6 @@{"name": "openvibe-sdk",-- "version": "0.125.0",++ "version": "0.126.0","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",@@@ -409,6 +409,12 @@"import": "./esm/video.mjs","default": "./src/video.js"},++ "./pics": {++ "types": "./types/pics.d.ts",++ "require": "./src/pics.js",++ "import": "./esm/pics.mjs",++ "default": "./src/pics.js"++ },"./work": {"types": "./types/work.d.ts","browser": null,@@@ -100,6 +100,7 @@ const ENTRIES = {'actor.mjs': '../src/actor.js','run.mjs': '../src/run.js','video.mjs': '../src/video.js',++ 'pics.mjs': '../src/pics.js','work.mjs': '../src/work.js','zone.mjs': '../src/zone.js','vip.mjs': '../src/vip.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.125.0';++const SDK_VERSION = '0.126.0';module.exports = {createClient,nnew file mode 100644@@@ -0,0 +1,35 @@++'use strict';++/** Pics discovery, private uploads, explicit publication and albums. */++function createPicsClient(client, { baseUrl = 'https://openvibe.pics' } = {}) {++ if (!client || typeof client.json !== 'function') throw new TypeError('createPicsClient requires an SDK core client');++ const call = (o) => client.json({ service: 'media-hub', audience: 'openvibe.download', baseUrl, ...o });++ const enc = (v) => encodeURIComponent(String(v));++ const image = (slug) => `/api/v1/images/${enc(slug)}`;++ const upload = (id) => `/api/v1/uploads/${enc(id)}`;++ const album = (slug) => `/api/v1/albums/${enc(slug)}`;++ return {++ gallery: (query = {}) => call({ path: '/api/v1/gallery', query }),++ get: (id) => call({ path: `/api/v1/pictures/${enc(id)}` }),++ mine: (query = {}) => call({ path: '/api/v1/me/images', query }),++ update: (slug, patch) => call({ method: 'PATCH', path: image(slug), json: patch, idempotent: true }),++ publish: (slug, { listed, tags } = {}) => call({ method: 'POST', path: `${image(slug)}/publication`, json: { listed, tags }, idempotent: true }),++ like: (slug, liked = true) => call({ method: liked ? 'PUT' : 'DELETE', path: `${image(slug)}/like`, idempotent: true }),++ remove: (slug) => call({ method: 'DELETE', path: image(slug) }),++ uploads: {++ create: (input) => call({ method: 'POST', path: '/api/v1/uploads', json: input, idempotencyKey: false }),++ get: (id) => call({ path: upload(id) }),++ chunk: (id, offset, bytes) => call({ method: 'PATCH', path: upload(id), headers: { 'content-type': 'application/octet-stream', 'upload-offset': String(offset) }, body: bytes, idempotent: false, retry: false }),++ complete: (id) => call({ method: 'POST', path: `${upload(id)}/complete`, idempotent: true }),++ cancel: (id) => call({ method: 'DELETE', path: upload(id) }),++ },++ albums: {++ list: () => call({ path: '/api/v1/albums' }),++ create: (input) => call({ method: 'POST', path: '/api/v1/albums', json: input, idempotencyKey: false }),++ get: (slug) => call({ path: album(slug) }),++ add: (slug, pictureSlug) => call({ method: 'POST', path: `${album(slug)}/images`, json: { image: pictureSlug }, idempotent: true }),++ removeImage: (slug, pictureSlug) => call({ method: 'DELETE', path: `${album(slug)}/images/${enc(pictureSlug)}` }),++ remove: (slug) => call({ method: 'DELETE', path: album(slug) }),++ },++ };++}++module.exports = { createPicsClient };@@@ -72,7 +72,7 @@ run([})().catch((err) => { console.error(err); process.exit(1); });`;const res = execFileSync(process.execPath, ['-e', script], { cwd: app, encoding: 'utf8' });assert.match(res, new RegExp(`consumer ok: ${subpaths.length} subpaths \\+ the browser bundle`));-- assert.equal(subpaths.length, 98); // Adds the upload client and the UI entry (0.123.0); the wallet came in 0.119.0.++ assert.equal(subpaths.length, 99); // Pics adds one subpath (0.125.0); the upload client and UI entry came in 0.123.0.assert.ok(!fs.existsSync(path.join(app, 'node_modules/openvibe-contracts')), 'the optional peer is not installed');} finally {fs.rmSync(tmp, { recursive: true, force: true });nnew file mode 100644@@@ -0,0 +1,21 @@++'use strict';++const assert = require('node:assert/strict');++const { run, stubServer } = require('./helpers');++const { createClient } = require('../src/core');++const { createPicsClient } = require('../src/pics');++run([[++ 'Pics routes preserve discovery queries, person audience and raw chunk bytes', async () => {++ const server = await stubServer(async (req, res, body) => { res.setHeader('content-type','application/json'); res.end(JSON.stringify({ ok:true })); });++ try {++ const pics = createPicsClient(createClient(), { baseUrl: server.url });++ await pics.gallery({sort:'rising',tag:'forest',cursor:'30'});++ await pics.uploads.chunk('pup_example', 7, Buffer.from([1,2,255]));++ await pics.publish('Abcdef23',{listed:true,tags:['forest']});++ await pics.like('Abcdef23',false);++ const [g,c,p,l] = server.requests;++ assert.match(g.url,/sort=rising/); assert.match(g.url,/cursor=30/);++ assert.equal(c.method,'PATCH'); assert.equal(c.headers['upload-offset'],'7'); assert.deepEqual([...c.body],[1,2,255]);++ assert.deepEqual(JSON.parse(p.body),{listed:true,tags:['forest']}); assert.equal(l.method,'DELETE');++ } finally { await server.close(); }++ }++]]);@@@ -50,6 +50,7 @@ export * as food from './food';export * as actor from './actor';export * as run from './run';export * as video from './video';++export * as pics from './pics';export * as work from './work';export * as zone from './zone';export * as play from './play';nnew file mode 100644@@@ -0,0 +1,16 @@++export interface Picture { id: string; slug: string; title: string; description: string; width: number; height: number; listed: boolean; tags: string[]; links: { page: string; direct: string; thumb: string; markdown: string; html: string; bbcode: string }; likes?: number; views?: number; author?: string }++export interface GalleryQuery { sort?: 'popular' | 'new' | 'rising'; q?: string; tag?: string; cursor?: string; limit?: number }++export interface Upload { id: string; size: number; offset: number; expires_at: string; image_id: string | null }++export interface Album { id: string; slug: string; title: string; description: string; images?: Picture[] }++export interface PicsClient {++ gallery(query?: GalleryQuery): Promise<{ pictures: Picture[]; next: string | null; sort: string }>;++ get(id: string): Promise<Picture>;++ mine(query?: { before?: string; limit?: number }): Promise<{ images: Picture[]; next: string | null }>;++ update(slug: string, patch: { title?: string; description?: string; alt?: string }): Promise<Picture>;++ publish(slug: string, input: { listed: boolean; tags?: string[] }): Promise<Picture>;++ like(slug: string, liked?: boolean): Promise<{ likes: number; views: number }>;++ remove(slug: string): Promise<void>;++ uploads: { create(input: { size: number; content_type: string; name?: string; title?: string }): Promise<Upload>; get(id: string): Promise<Upload>; chunk(id: string, offset: number, bytes: Uint8Array): Promise<Upload>; complete(id: string): Promise<Picture>; cancel(id: string): Promise<void> };++ albums: { list(): Promise<{ albums: Album[] }>; create(input: { title: string; description?: string }): Promise<Album>; get(slug: string): Promise<Album>; add(slug: string, pictureSlug: string): Promise<unknown>; removeImage(slug: string, pictureSlug: string): Promise<void>; remove(slug: string): Promise<void> };++}++export function createPicsClient(client: { json(options: Record<string, unknown>): Promise<any> }, options?: { baseUrl?: string }): PicsClient;
@@ -1,5 +1,9 @@
@@ -1,5 +1,9 @@
# Changelog
# Changelog
+## 0.126.0 (2026-11-09)
+
+- Add `openvibe-sdk/pics`: the public gallery and picture reads, private uploads with resumable chunks, publication, likes and albums on OpenVibe.Pics.
+
## 0.125.0 (2026-11-09)
## 0.125.0 (2026-11-09)
- openvibe-sdk/blog: posts are written and read as Markdown or as blocks (publishing.block@1), ?format=blocks
- openvibe-sdk/blog: posts are written and read as Markdown or as blocks (publishing.block@1), ?format=blocks
@@ -10,7 +10,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags
@@ -10,7 +10,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags
> Supported browser and server clients for the OpenVibe platform.
> Supported browser and server clients for the OpenVibe platform.
-**Status:** alpha, v0.125.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.
+**Status:** alpha, v0.126.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.
**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.
**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.
**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.
@@ -19,7 +19,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags
@@ -19,7 +19,7 @@ npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags
After v0.97.1 is tagged:
After v0.97.1 is tagged:
```bash
```bash
-npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.125.0
+npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.126.0
```
```
It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).
It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).
@@ -693,3 +693,20 @@ await exchange.arena.vote(round.id, 'left');
@@ -693,3 +693,20 @@ await exchange.arena.vote(round.id, 'left');
Camera choices can be read or saved on behalf of a signed-in person with `ov.cam.settings.get({ token })` and
Camera choices can be read or saved on behalf of a signed-in person with `ov.cam.settings.get({ token })` and
`ov.cam.settings.put({ token, values: { facing, lens, mic, resolution, mirror } })`. Server callers should forward
`ov.cam.settings.put({ token, values: { facing, lens, mic, resolution, mirror } })`. Server callers should forward
the person's Network token; Cam keeps these preferences per person, and a missing device id is only a hint.
the person's Network token; Cam keeps these preferences per person, and a missing device id is only a hint.
+
+### Pictures
+
+```js
+const { createPicsClient } = require('openvibe-sdk/pics');
+const pics = createPicsClient(client);
+const gallery = await pics.gallery({ sort: 'new', tag: 'landscape' });
+const session = await pics.uploads.create({ size: bytes.length, content_type: 'image/png', name: 'photo.png' });
+// Send at most 1 MiB per chunk; read uploads.get(id).offset after an interrupted request.
+await pics.uploads.chunk(session.id, 0, bytes);
+const picture = await pics.uploads.complete(session.id); // private
+await pics.publish(picture.slug, { listed: true, tags: ['landscape'] });
+```
+
+`pics.albums` lists, creates and manages albums; `pics.like(slug, false)` removes a
+like. Upload and write calls require a person's Network token. Gallery reads are
+public. Resumable completion and likes are idempotent; creation is not retried.
@@ -3,17 +3,18 @@
@@ -3,17 +3,18 @@
"stage": "alpha",
"stage": "alpha",
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
- "version": "0.125.0",
- "plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
+ "version": "0.126.0",
+ "plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), \u00a73.2; roadmap \u00a730",
"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",
"contracts": "openvibe-contracts v0.338.0",
"contracts": "openvibe-contracts v0.338.0",
"features": [
"features": [
"menu (openvibe-sdk/menu): menuCard() builds one validated frame.menu-card@1 (plain text, the contract caps, https links on the site own origin or an openvibe.* host, an optional item picture) and menuRoute() is the Express/node:http GET /.well-known/openvibe-menu.json handler (public cache, CORS *, [] and one log line when the cards cannot be built)",
"menu (openvibe-sdk/menu): menuCard() builds one validated frame.menu-card@1 (plain text, the contract caps, https links on the site own origin or an openvibe.* host, an optional item picture) and menuRoute() is the Express/node:http GET /.well-known/openvibe-menu.json handler (public cache, CORS *, [] and one log line when the cards cannot be built)",
"cam (openvibe-sdk/cam): defineOvCamera({ src }) injects the <ov-camera> element from openvibe.cam once and resolves to its class, with typed attributes, events and methods (start/stop/capture/flip); browser-only, upload=\"off\" by default",
"cam (openvibe-sdk/cam): defineOvCamera({ src }) injects the <ov-camera> element from openvibe.cam once and resolves to its class, with typed attributes, events and methods (start/stop/capture/flip); browser-only, upload=\"off\" by default",
+ "pics (openvibe-sdk/pics): the public gallery and one picture's reads, private uploads with resumable chunks, the owner's explicit publication, likes and albums on OpenVibe.Pics; reads are public, writes take a person's Network token",
"streams (openvibe-sdk/streams, S7): schedule(), listSchedules(), cancelSchedule() and remindSchedule() for scheduled public agent streams; postStep() and steps.post() from any harness; digest.read() and poll.open()/poll.result() for a Co-pilot service token; onControl() for pause, curtain, mute and end",
"streams (openvibe-sdk/streams, S7): schedule(), listSchedules(), cancelSchedule() and remindSchedule() for scheduled public agent streams; postStep() and steps.post() from any harness; digest.read() and poll.open()/poll.result() for a Co-pilot service token; onControl() for pause, curtain, mute and end",
"reports (openvibe-sdk/reports, S7): write() a session report with the person's own token, private by default and idempotent per source and task ref, and read() one the caller may see; examples/cli-agent-stream.js streams any CLI command's run with steps",
"reports (openvibe-sdk/reports, S7): write() a session report with the person's own token, private by default and idempotent per source and task ref, and read() one the caller may see; examples/cli-agent-stream.js streams any CLI command's run with steps",
- "chat-apps (openvibe-sdk/chat-apps): the three chat apps (Telegram, Discord, Slack) in one place — the delivery checks (Telegram's secret token, Slack's v0 signing, Discord's Ed25519), the button callback (signed once, used once, expiring), each app's payload as the same shape and splitText() for a long answer",
+ "chat-apps (openvibe-sdk/chat-apps): the three chat apps (Telegram, Discord, Slack) in one place \u2014 the delivery checks (Telegram's secret token, Slack's v0 signing, Discord's Ed25519), the button callback (signed once, used once, expiring), each app's payload as the same shape and splitText() for a long answer",
"exchange (openvibe-sdk/exchange): the buyer side (chat, models, offers, presets, usage, receipts) and the seller side (offer, endpointOffers, removeEndpointOffer, earnings, verification) of OpenVibe.Exchange, with the platform webhook signature check; an offer is a Link machine's Network report or a standalone OpenAI-compatible endpoint registered on the public seller API",
"exchange (openvibe-sdk/exchange): the buyer side (chat, models, offers, presets, usage, receipts) and the seller side (offer, endpointOffers, removeEndpointOffer, earnings, verification) of OpenVibe.Exchange, with the platform webhook signature check; an offer is a Link machine's Network report or a standalone OpenAI-compatible endpoint registered on the public seller API",
"locker (openvibe-sdk/locker, openvibe-sdk/locker/server): the Locker envelope (locker.envelope@1) for end-to-end encryption, WebCrypto only, in Node and the browser: seal/open for text and bytes, sealStream/openStream, the three locks (people, password, link), person key pairs (ECDH P-256, ECDSA P-256), the eight-word fingerprint, wrapFor and rewrap, recoveryWords for the phrase to write down, opening a people lock by key, inspect for what an envelope says about itself, and the header checks a service runs without a key",
"locker (openvibe-sdk/locker, openvibe-sdk/locker/server): the Locker envelope (locker.envelope@1) for end-to-end encryption, WebCrypto only, in Node and the browser: seal/open for text and bytes, sealStream/openStream, the three locks (people, password, link), person key pairs (ECDH P-256, ECDSA P-256), the eight-word fingerprint, wrapFor and rewrap, recoveryWords for the phrase to write down, opening a people lock by key, inspect for what an envelope says about itself, and the header checks a service runs without a key",
"the 1.0 core surface is frozen now, before 1.0 (core, auth, events, db, media): docs/one-point-oh.json gives every export its tier, test/surface.test.js pins the exact names, and removing or renaming one needs a major release; createCodeVerifier and pkceChallenge left in 0.86.0",
"the 1.0 core surface is frozen now, before 1.0 (core, auth, events, db, media): docs/one-point-oh.json gives every export its tier, test/surface.test.js pins the exact names, and removing or renaming one needs a major release; createCodeVerifier and pkceChallenge left in 0.86.0",
@@ -37,7 +38,7 @@
@@ -37,7 +38,7 @@
"valkey, cache, queue, pubsub and a shared limits store on Valkey 9, each with an in-process twin for tests",
"valkey, cache, queue, pubsub and a shared limits store on Valkey 9, each with an in-process twin for tests",
"commerce (openvibe-sdk/commerce): the shared Billing client for Tips and VIP, with intentKey and receipt",
"commerce (openvibe-sdk/commerce): the shared Billing client for Tips and VIP, with intentKey and receipt",
"events: the PostgreSQL outbox (lease-claimed, several relays per table) and inbox (handler in its transaction)",
"events: the PostgreSQL outbox (lease-claimed, several relays per table) and inbox (handler in its transaction)",
- "usage: createUsageReporter (openvibe-sdk/usage), the shared platform.usage-sample@1 reporter — validated readings queued idempotency-keyed in an outbox and relayed to Billing with retry, never dropped, refused readings kept",
+ "usage: createUsageReporter (openvibe-sdk/usage), the shared platform.usage-sample@1 reporter \u2014 validated readings queued idempotency-keyed in an outbox and relayed to Billing with retry, never dropped, refused readings kept",
"the HTTP telemetry kit (openvibe-sdk/service): the platform.telemetry-sample@1 middleware aggregated per route|method|status_class per flush, with active-requests, p95 and event-loop-lag gauges, lifted from Network's server/telemetry.js and server/observability.js",
"the HTTP telemetry kit (openvibe-sdk/service): the platform.telemetry-sample@1 middleware aggregated per route|method|status_class per flush, with active-requests, p95 and event-loop-lag gauges, lifted from Network's server/telemetry.js and server/observability.js",
"the telemetry sink (openvibe-sdk/telemetry): telemetrySchema() DDL, createPgTelemetrySink() and pruneTelemetry(), a per-service PostgreSQL table every service stores platform.telemetry-sample@1 into",
"the telemetry sink (openvibe-sdk/telemetry): telemetrySchema() DDL, createPgTelemetrySink() and pruneTelemetry(), a per-service PostgreSQL table every service stores platform.telemetry-sample@1 into",
"account-data (openvibe-sdk/account-data, ADR-033): telemetry_samples covered once by the SDK (exported as telemetry.json, anonymized on deletion), and uncovered() listing every table with a person column that no table map, task-keyed list or reasoned exemption covers",
"account-data (openvibe-sdk/account-data, ADR-033): telemetry_samples covered once by the SDK (exported as telemetry.json, anonymized on deletion), and uncovered() listing every table with a person column that no table map, task-keyed list or reasoned exemption covers",
@@ -46,8 +47,8 @@
@@ -46,8 +47,8 @@
"the AI Exchange face of the one planner (openvibe-sdk/placement planAi, off by default): the hard gates (exact artifact, commercial right, privacy class, trust tier, capabilities, context, health, cost ceiling), the expected cost per success in integer nano-USD, sparse quality evidence marked and not trusted, and an explanation that never carries a secret or a host; the money math in openvibe-sdk/cost (nanoUsd, chargeTokens, expectedCostNano, costPerSuccessNano)",
"the AI Exchange face of the one planner (openvibe-sdk/placement planAi, off by default): the hard gates (exact artifact, commercial right, privacy class, trust tier, capabilities, context, health, cost ceiling), the expected cost per success in integer nano-USD, sparse quality evidence marked and not trusted, and an explanation that never carries a secret or a host; the money math in openvibe-sdk/cost (nanoUsd, chargeTokens, expectedCostNano, costPerSuccessNano)",
"consent (openvibe-sdk/consent): the L24 check before any use of a person's content or likeness for AI or data; allowed() and filter() (batches of 500), onChange() drops a subject's answers on network.consent.changed, mock() for tests; fails closed, caches 60 s, server-side",
"consent (openvibe-sdk/consent): the L24 check before any use of a person's content or likeness for AI or data; allowed() and filter() (batches of 500), onChange() drops a subject's answers on network.consent.changed, mock() for tests; fails closed, caches 60 s, server-side",
"openvibe-sdk/machines: the network's machines hub (list, get, catalogue, pair, rename, roles, revoke) against OpenVibe.Network, for a person and for the apps they consented to",
"openvibe-sdk/machines: the network's machines hub (list, get, catalogue, pair, rename, roles, revoke) against OpenVibe.Network, for a person and for the apps they consented to",
- "vibes (openvibe-sdk/vibes): Vibes Pay charges (create with the app reference as the Idempotency-Key, get, refund) and constant-time webhook verification (X-OpenVibe-Signature-V2, ±300 s) for developer apps",
- "streams (openvibe-sdk/streams): the Agent Streams plan S1 — start an agent stream on OpenVibe.Live (live.agent-stream@1) with its publish ticket, renew and revoke that ticket (the kill switch), poll state, post the last frame, set the curtain, switch visibility, end it and mint the view ticket; the publish ticket is a bearer of its own on the desk routes"
+ "vibes (openvibe-sdk/vibes): Vibes Pay charges (create with the app reference as the Idempotency-Key, get, refund) and constant-time webhook verification (X-OpenVibe-Signature-V2, \u00b1300 s) for developer apps",
+ "streams (openvibe-sdk/streams): the Agent Streams plan S1 \u2014 start an agent stream on OpenVibe.Live (live.agent-stream@1) with its publish ticket, renew and revoke that ticket (the kill switch), poll state, post the last frame, set the curtain, switch visibility, end it and mint the view ticket; the publish ticket is a bearer of its own on the desk routes"
],
],
"updated": "2026-11-09"
"updated": "2026-11-09"
}
}
@@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
-// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.125.0, MIT). Do not edit.
+// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.126.0, 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, locker, registry, trust, modules, realtime, media, cam, mediaUpload, ui, community, homeFeed, bot, search, bench, meme, vision, themes, design, redact, chat, ai, credentials, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
// (auth, locker, registry, trust, modules, realtime, media, cam, mediaUpload, ui, community, homeFeed, bot, search, bench, meme, vision, themes, design, redact, chat, ai, credentials, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
/* eslint-disable */
/* eslint-disable */
@@ -56,7 +56,7 @@ const { paginate, offsetPager } = __load(7);
@@ -56,7 +56,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.125.0';
+const SDK_VERSION = '0.126.0';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -282,6 +282,11 @@
@@ -282,6 +282,11 @@
},
},
"paginate": "function",
"paginate": "function",
"parseTraceparent": "function",
"parseTraceparent": "function",
+ "pics": {
+ "object": [
+ "createPicsClient"
+ ]
+ },
"play": {
"play": {
"object": [
"object": [
"createPlayClient"
"createPlayClient"
@@ -930,6 +935,9 @@
@@ -930,6 +935,9 @@
"openvibe-sdk/openre": {
"openvibe-sdk/openre": {
"createOpenReClient": "function"
"createOpenReClient": "function"
},
},
+ "openvibe-sdk/pics": {
+ "createPicsClient": "function"
+ },
"openvibe-sdk/placement": {
"openvibe-sdk/placement": {
"DEFAULT_P_FLOOR": "number",
"DEFAULT_P_FLOOR": "number",
"DEFAULT_TRUST": "array",
"DEFAULT_TRUST": "array",
@@ -13,7 +13,8 @@ export const {
@@ -13,7 +13,8 @@ export const {
projects, resources, vip, quest, live, inventory,
projects, resources, vip, quest, live, inventory,
tips, blog, reviews, wiki, coupons, deals,
tips, blog, reviews, wiki, coupons, deals,
shoes, trade, space, host, news, food,
shoes, trade, space, host, news, food,
- actor, run, video, work, zone, play,
- download, openre, geo, usage, commerce, telemetry,
+ actor, run, video, pics, work, zone,
+ play, download, openre, geo, usage, commerce,
+ telemetry,
} = cjs;
} = cjs;
export default cjs;
export default cjs;
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+// Generated by scripts/esm.js from src/pics.js. Do not edit.
+import cjs from '../src/pics.js';
+
+export const {
+ createPicsClient,
+} = cjs;
+export default cjs;
@@ -60,6 +60,7 @@ module.exports = {
@@ -60,6 +60,7 @@ module.exports = {
actor: require('./src/actor'),
actor: require('./src/actor'),
run: require('./src/run'),
run: require('./src/run'),
video: require('./src/video'),
video: require('./src/video'),
+ pics: require('./src/pics'),
work: require('./src/work'),
work: require('./src/work'),
zone: require('./src/zone'),
zone: require('./src/zone'),
play: require('./src/play'),
play: require('./src/play'),
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.125.0",
+ "version": "0.126.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.125.0",
+ "version": "0.126.0",
"license": "MIT",
"license": "MIT",
"bin": {
"bin": {
"openvibe-download": "bin/openvibe-download.js"
"openvibe-download": "bin/openvibe-download.js"
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.125.0",
+ "version": "0.126.0",
"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",
@@ -409,6 +409,12 @@
@@ -409,6 +409,12 @@
"import": "./esm/video.mjs",
"import": "./esm/video.mjs",
"default": "./src/video.js"
"default": "./src/video.js"
},
},
+ "./pics": {
+ "types": "./types/pics.d.ts",
+ "require": "./src/pics.js",
+ "import": "./esm/pics.mjs",
+ "default": "./src/pics.js"
+ },
"./work": {
"./work": {
"types": "./types/work.d.ts",
"types": "./types/work.d.ts",
"browser": null,
"browser": null,
@@ -100,6 +100,7 @@ const ENTRIES = {
@@ -100,6 +100,7 @@ const ENTRIES = {
'actor.mjs': '../src/actor.js',
'actor.mjs': '../src/actor.js',
'run.mjs': '../src/run.js',
'run.mjs': '../src/run.js',
'video.mjs': '../src/video.js',
'video.mjs': '../src/video.js',
+ 'pics.mjs': '../src/pics.js',
'work.mjs': '../src/work.js',
'work.mjs': '../src/work.js',
'zone.mjs': '../src/zone.js',
'zone.mjs': '../src/zone.js',
'vip.mjs': '../src/vip.js',
'vip.mjs': '../src/vip.js',
@@ -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.125.0';
+const SDK_VERSION = '0.126.0';
module.exports = {
module.exports = {
createClient,
createClient,
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+'use strict';
+/** Pics discovery, private uploads, explicit publication and albums. */
+function createPicsClient(client, { baseUrl = 'https://openvibe.pics' } = {}) {
+ if (!client || typeof client.json !== 'function') throw new TypeError('createPicsClient requires an SDK core client');
+ const call = (o) => client.json({ service: 'media-hub', audience: 'openvibe.download', baseUrl, ...o });
+ const enc = (v) => encodeURIComponent(String(v));
+ const image = (slug) => `/api/v1/images/${enc(slug)}`;
+ const upload = (id) => `/api/v1/uploads/${enc(id)}`;
+ const album = (slug) => `/api/v1/albums/${enc(slug)}`;
+ return {
+ gallery: (query = {}) => call({ path: '/api/v1/gallery', query }),
+ get: (id) => call({ path: `/api/v1/pictures/${enc(id)}` }),
+ mine: (query = {}) => call({ path: '/api/v1/me/images', query }),
+ update: (slug, patch) => call({ method: 'PATCH', path: image(slug), json: patch, idempotent: true }),
+ publish: (slug, { listed, tags } = {}) => call({ method: 'POST', path: `${image(slug)}/publication`, json: { listed, tags }, idempotent: true }),
+ like: (slug, liked = true) => call({ method: liked ? 'PUT' : 'DELETE', path: `${image(slug)}/like`, idempotent: true }),
+ remove: (slug) => call({ method: 'DELETE', path: image(slug) }),
+ uploads: {
+ create: (input) => call({ method: 'POST', path: '/api/v1/uploads', json: input, idempotencyKey: false }),
+ get: (id) => call({ path: upload(id) }),
+ chunk: (id, offset, bytes) => call({ method: 'PATCH', path: upload(id), headers: { 'content-type': 'application/octet-stream', 'upload-offset': String(offset) }, body: bytes, idempotent: false, retry: false }),
+ complete: (id) => call({ method: 'POST', path: `${upload(id)}/complete`, idempotent: true }),
+ cancel: (id) => call({ method: 'DELETE', path: upload(id) }),
+ },
+ albums: {
+ list: () => call({ path: '/api/v1/albums' }),
+ create: (input) => call({ method: 'POST', path: '/api/v1/albums', json: input, idempotencyKey: false }),
+ get: (slug) => call({ path: album(slug) }),
+ add: (slug, pictureSlug) => call({ method: 'POST', path: `${album(slug)}/images`, json: { image: pictureSlug }, idempotent: true }),
+ removeImage: (slug, pictureSlug) => call({ method: 'DELETE', path: `${album(slug)}/images/${enc(pictureSlug)}` }),
+ remove: (slug) => call({ method: 'DELETE', path: album(slug) }),
+ },
+ };
+}
+module.exports = { createPicsClient };
@@ -72,7 +72,7 @@ run([
@@ -72,7 +72,7 @@ run([
})().catch((err) => { console.error(err); process.exit(1); });`;
})().catch((err) => { console.error(err); process.exit(1); });`;
const res = execFileSync(process.execPath, ['-e', script], { cwd: app, encoding: 'utf8' });
const res = execFileSync(process.execPath, ['-e', script], { cwd: app, encoding: 'utf8' });
assert.match(res, new RegExp(`consumer ok: ${subpaths.length} subpaths \\+ the browser bundle`));
assert.match(res, new RegExp(`consumer ok: ${subpaths.length} subpaths \\+ the browser bundle`));
- assert.equal(subpaths.length, 98); // Adds the upload client and the UI entry (0.123.0); the wallet came in 0.119.0.
+ assert.equal(subpaths.length, 99); // Pics adds one subpath (0.125.0); the upload client and UI entry came in 0.123.0.
assert.ok(!fs.existsSync(path.join(app, 'node_modules/openvibe-contracts')), 'the optional peer is not installed');
assert.ok(!fs.existsSync(path.join(app, 'node_modules/openvibe-contracts')), 'the optional peer is not installed');
} finally {
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
fs.rmSync(tmp, { recursive: true, force: true });
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+'use strict';
+const assert = require('node:assert/strict');
+const { run, stubServer } = require('./helpers');
+const { createClient } = require('../src/core');
+const { createPicsClient } = require('../src/pics');
+run([[
+ 'Pics routes preserve discovery queries, person audience and raw chunk bytes', async () => {
+ const server = await stubServer(async (req, res, body) => { res.setHeader('content-type','application/json'); res.end(JSON.stringify({ ok:true })); });
+ try {
+ const pics = createPicsClient(createClient(), { baseUrl: server.url });
+ await pics.gallery({sort:'rising',tag:'forest',cursor:'30'});
+ await pics.uploads.chunk('pup_example', 7, Buffer.from([1,2,255]));
+ await pics.publish('Abcdef23',{listed:true,tags:['forest']});
+ await pics.like('Abcdef23',false);
+ const [g,c,p,l] = server.requests;
+ assert.match(g.url,/sort=rising/); assert.match(g.url,/cursor=30/);
+ assert.equal(c.method,'PATCH'); assert.equal(c.headers['upload-offset'],'7'); assert.deepEqual([...c.body],[1,2,255]);
+ assert.deepEqual(JSON.parse(p.body),{listed:true,tags:['forest']}); assert.equal(l.method,'DELETE');
+ } finally { await server.close(); }
+ }
+]]);
@@ -50,6 +50,7 @@ export * as food from './food';
@@ -50,6 +50,7 @@ export * as food from './food';
export * as actor from './actor';
export * as actor from './actor';
export * as run from './run';
export * as run from './run';
export * as video from './video';
export * as video from './video';
+export * as pics from './pics';
export * as work from './work';
export * as work from './work';
export * as zone from './zone';
export * as zone from './zone';
export * as play from './play';
export * as play from './play';
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+export interface Picture { id: string; slug: string; title: string; description: string; width: number; height: number; listed: boolean; tags: string[]; links: { page: string; direct: string; thumb: string; markdown: string; html: string; bbcode: string }; likes?: number; views?: number; author?: string }
+export interface GalleryQuery { sort?: 'popular' | 'new' | 'rising'; q?: string; tag?: string; cursor?: string; limit?: number }
+export interface Upload { id: string; size: number; offset: number; expires_at: string; image_id: string | null }
+export interface Album { id: string; slug: string; title: string; description: string; images?: Picture[] }
+export interface PicsClient {
+ gallery(query?: GalleryQuery): Promise<{ pictures: Picture[]; next: string | null; sort: string }>;
+ get(id: string): Promise<Picture>;
+ mine(query?: { before?: string; limit?: number }): Promise<{ images: Picture[]; next: string | null }>;
+ update(slug: string, patch: { title?: string; description?: string; alt?: string }): Promise<Picture>;
+ publish(slug: string, input: { listed: boolean; tags?: string[] }): Promise<Picture>;
+ like(slug: string, liked?: boolean): Promise<{ likes: number; views: number }>;
+ remove(slug: string): Promise<void>;
+ uploads: { create(input: { size: number; content_type: string; name?: string; title?: string }): Promise<Upload>; get(id: string): Promise<Upload>; chunk(id: string, offset: number, bytes: Uint8Array): Promise<Upload>; complete(id: string): Promise<Picture>; cancel(id: string): Promise<void> };
+ albums: { list(): Promise<{ albums: Album[] }>; create(input: { title: string; description?: string }): Promise<Album>; get(slug: string): Promise<Album>; add(slug: string, pictureSlug: string): Promise<unknown>; removeImage(slug: string, pictureSlug: string): Promise<void>; remove(slug: string): Promise<void> };
+}
+export function createPicsClient(client: { json(options: Record<string, unknown>): Promise<any> }, options?: { baseUrl?: string }): PicsClient;