/** * WP Data Optimizer — REST API JavaScript SDK * * Lightweight client for /wp-json/wpdo/v1/ endpoints. * No dependencies required. * * Usage: * const wpdo = new WpdoClient(); * * // Fetch listings (Zone A) * const { items, total } = await wpdo.getListings({ hp_price_min: 100, per_page: 20 }); * * // Single listing (Zone A + Zone C) * const listing = await wpdo.getListing(123); * * // View count (Zone B) * const { view_count } = await wpdo.getStats(123); * * // Increment view count (Zone B, requires WP REST nonce) * const { view_count: updated } = await wpdo.incrementView(123); * * // Iterate all pages * for await (const page of wpdo.paginateListings({ hp_featured: 1 })) { * console.log(page.items); * } * * @package WP_Data_Optimizer */ /* global wpdo_sdk_config */ ( function ( global ) { 'use strict'; /** * WpdoClient — REST API wrapper for WP Data Optimizer. * * @param {object} [options] * @param {string} [options.baseUrl] REST base URL (default: auto-detected from wpdo_sdk_config or /wp-json) * @param {string} [options.nonce] WP REST nonce for authenticated requests * @param {string} [options.postType] Default post type (default: 'hp_listing') */ function WpdoClient( options ) { options = options || {}; var config = ( typeof wpdo_sdk_config !== 'undefined' ) ? wpdo_sdk_config : {}; this._base = options.baseUrl || config.rest_url || '/wp-json/wpdo/v1'; this._nonce = options.nonce || config.nonce || ''; this._postType = options.postType || config.post_type || 'hp_listing'; } // ── Core fetch ──────────────────────────────────────────────────────────── /** * Internal GET fetch helper. * * @param {string} path Relative path (e.g. '/listings') * @param {object} params Query parameters * @return {Promise<{data: *, headers: Headers, status: number}>} */ WpdoClient.prototype._fetch = function ( path, params ) { var url = this._base + path; if ( params && Object.keys( params ).length ) { var qs = Object.keys( params ) .filter( function ( k ) { return params[ k ] !== null && params[ k ] !== undefined && params[ k ] !== ''; } ) .map( function ( k ) { return encodeURIComponent( k ) + '=' + encodeURIComponent( params[ k ] ); } ) .join( '&' ); if ( qs ) { url += '?' + qs; } } var headers = { 'Content-Type': 'application/json' }; if ( this._nonce ) { headers[ 'X-WP-Nonce' ] = this._nonce; } return fetch( url, { headers: headers } ).then( function ( res ) { return res.json().then( function ( data ) { return { data: data, headers: res.headers, status: res.status }; } ); } ); }; /** * Internal POST fetch helper. * * @param {string} path Relative path * @param {object} body JSON body (optional) * @return {Promise<{data: *, status: number}>} */ WpdoClient.prototype._post = function ( path, body ) { var headers = { 'Content-Type': 'application/json' }; if ( this._nonce ) { headers[ 'X-WP-Nonce' ] = this._nonce; } return fetch( this._base + path, { method: 'POST', headers: headers, body: body ? JSON.stringify( body ) : null, } ).then( function ( res ) { return res.json().then( function ( data ) { return { data: data, status: res.status }; } ); } ); }; // ── Public API ──────────────────────────────────────────────────────────── /** * Fetch a page of listings from Zone A. * * @param {object} [params] * @param {string} [params.post_type] Default: configured post type * @param {number} [params.per_page] 1–100, default 20 * @param {number} [params.page] Default 1 * @param {string} [params.orderby] Column name, default 'post_id' * @param {string} [params.order] 'ASC' | 'DESC', default 'DESC' * @param {number} [params.*_min] Numeric range filter (e.g. hp_price_min) * @param {number} [params.*_max] Numeric range filter (e.g. hp_price_max) * @param {*} [params.*] Exact match filter (e.g. hp_featured: 1) * @return {Promise<{items: Array, total: number, totalPages: number}>} */ WpdoClient.prototype.getListings = function ( params ) { var merged = Object.assign( { post_type: this._postType }, params || {} ); return this._fetch( '/listings', merged ).then( function ( res ) { return { items: res.data, total: parseInt( res.headers.get( 'X-WP-Total' ) || '0', 10 ), totalPages: parseInt( res.headers.get( 'X-WP-TotalPages' ) || '0', 10 ), status: res.status, }; } ); }; /** * Fetch a single listing (Zone A + Zone C merged). * * @param {number} id Post ID * @return {Promise} */ WpdoClient.prototype.getListing = function ( id ) { return this._fetch( '/listings/' + id, null ).then( function ( res ) { return res.data; } ); }; /** * Fetch Zone B view count for a post. * * @param {number} id Post ID * @return {Promise<{post_id: number, view_count: number}>} */ WpdoClient.prototype.getStats = function ( id ) { return this._fetch( '/stats/' + id, null ).then( function ( res ) { return res.data; } ); }; /** * Increment Zone B view count for a post (requires WP REST nonce). * * The nonce must be passed via the `nonce` constructor option or * via `wpdo_sdk_config.nonce` (wp_create_nonce('wp_rest')). * * @param {number} id Post ID * @return {Promise<{post_id: number, view_count: number}>} * * Example: * // Auto-tracks a view when a listing page loads: * document.addEventListener('DOMContentLoaded', () => { * const postId = parseInt(document.body.dataset.postId); * if (postId) wpdo.incrementView(postId); * }); */ WpdoClient.prototype.incrementView = function ( id ) { return this._post( '/listings/' + id + '/view', null ).then( function ( res ) { return res.data; } ); }; /** * Async generator — iterates every page of listings. * * @param {object} [params] Same params as getListings (page is managed internally) * @yields {{items: Array, total: number, page: number, totalPages: number}} * * Example: * for await (const page of wpdo.paginateListings({ hp_featured: 1 })) { * page.items.forEach(item => console.log(item)); * } */ WpdoClient.prototype.paginateListings = async function * ( params ) { var page = 1; var total = Infinity; var perPage = ( params && params.per_page ) ? params.per_page : 20; while ( ( page - 1 ) * perPage < total ) { var merged = Object.assign( {}, params || {}, { page: page } ); var result = await this.getListings( merged ); total = result.total; yield { items: result.items, total: total, page: page, totalPages: result.totalPages }; if ( page >= result.totalPages ) { break; } page++; } }; // ── Export ──────────────────────────────────────────────────────────────── global.WpdoClient = WpdoClient; // Auto-instantiate as window.wpdo if config is present. if ( typeof wpdo_sdk_config !== 'undefined' ) { global.wpdo = new WpdoClient(); } }( window ) );