@dismissible/react-client 2.0.0-canary.5.88c27fb → 2.1.0-canary.6.0a7a428

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(l,h){typeof exports=="object"&&typeof module!="undefined"?h(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],h):(l=typeof globalThis!="undefined"?globalThis:l||self,h(l.DismissibleClient={},l.React.jsxRuntime,l.React))})(this,(function(l,h,a){"use strict";var $e=Object.defineProperty,Te=Object.defineProperties;var Ie=Object.getOwnPropertyDescriptors;var X=Object.getOwnPropertySymbols;var de=Object.prototype.hasOwnProperty,he=Object.prototype.propertyIsEnumerable;var fe=(l,h,a)=>h in l?$e(l,h,{enumerable:!0,configurable:!0,writable:!0,value:a}):l[h]=a,w=(l,h)=>{for(var a in h||(h={}))de.call(h,a)&&fe(l,a,h[a]);if(X)for(var a of X(h))he.call(h,a)&&fe(l,a,h[a]);return l},A=(l,h)=>Te(l,Ie(h));var ee=(l,h)=>{var a={};for(var R in l)de.call(l,R)&&h.indexOf(R)<0&&(a[R]=l[R]);if(l!=null&&X)for(var R of X(l))h.indexOf(R)<0&&he.call(l,R)&&(a[R]=l[R]);return a};var j=(l,h,a)=>new Promise((R,z)=>{var _=x=>{try{F(a.next(x))}catch(L){z(L)}},J=x=>{try{F(a.throw(x))}catch(L){z(L)}},F=x=>x.done?R(x.value):Promise.resolve(x.value).then(_,J);F((a=a.apply(l,h)).next())});const R=(t,r,e)=>{try{const s=`${r}_${t}`,i=localStorage.getItem(s);if(!i)return null;const{data:n,timestamp:o}=JSON.parse(i);return e&&Date.now()-o>e?(localStorage.removeItem(s),null):n}catch(s){return null}},z=(t,r,e)=>{try{const s=`${e}_${t}`,i={data:r,timestamp:Date.now()};localStorage.setItem(s,JSON.stringify(i))}catch(s){console.warn("Failed to cache dismissible item:",s)}},_=(t,r)=>{try{const e=`${r}_${t}`;localStorage.removeItem(e)}catch(e){console.warn("Failed to remove cached dismissible item:",e)}},J=a.createContext(null),F=()=>{const t=a.useContext(J);if(!t)throw new Error("useDismissibleContext must be used within a DismissibleProvider");return t},x="dismissible",L=(t,r={})=>{const{initialData:e,enableCache:s=!0,cachePrefix:i=x,cacheExpiration:n}=r,o=F(),{userId:c,client:y,baseUrl:S}=o,m=`${c}-${t}`,I=a.useRef({enableCache:s,cachePrefix:i,cacheExpiration:n}),d=a.useRef(t),f=a.useRef(m),D=a.useRef(null),[H,q]=a.useState(!1),[N,v]=a.useState(),[$,T]=a.useState(()=>{if(e)return e;if(s){const u=R(m,i,n);if(u)return u}}),U=a.useCallback(()=>j(null,null,function*(){var C;if(s){const p=R(m,i,n);if(p!=null&&p.dismissedAt){T(p),q(!1);return}}(C=D.current)==null||C.abort();const u=new AbortController;D.current=u,q(!0),v(void 0);try{const p=yield o.getAuthHeaders(),M=yield y.getOrCreate({userId:c,itemId:t,baseUrl:S,authHeaders:p,signal:u.signal});T(M),s&&z(m,M,i)}catch(p){if(p instanceof Error&&p.name==="AbortError")return;v(p instanceof Error?p:new Error("Unknown error occurred"))}finally{q(!1)}}),[t,c,m,s,i,n,y,S,o]);a.useEffect(()=>{const u=d.current!==t,C=f.current!==m;u||C?(d.current=t,f.current=m,U()):e||U()},[t,m,e,U]),a.useEffect(()=>()=>{var u;(u=D.current)==null||u.abort()},[]),a.useEffect(()=>{const u=I.current;(u.enableCache!==s||u.cachePrefix!==i||u.cacheExpiration!==n)&&(u.cachePrefix!==i&&_(m,u.cachePrefix),!s&&u.enableCache&&_(m,u.cachePrefix),I.current={enableCache:s,cachePrefix:i,cacheExpiration:n},U())},[s,i,n,m,U]);const G=a.useCallback(()=>j(null,null,function*(){v(void 0);try{const u=yield o.getAuthHeaders(),C=yield y.dismiss({userId:c,itemId:t,baseUrl:S,authHeaders:u});T(C),s&&z(m,C,i)}catch(u){throw v(u instanceof Error?u:new Error("Failed to dismiss item")),u}}),[t,c,m,s,i,y,S,o]),Y=a.useCallback(()=>j(null,null,function*(){v(void 0);try{const u=yield o.getAuthHeaders(),C=yield y.restore({userId:c,itemId:t,baseUrl:S,authHeaders:u});T(C),s&&z(m,C,i)}catch(u){throw v(u instanceof Error?u:new Error("Failed to restore item")),u}}),[t,c,m,s,i,y,S,o]);return{dismissedAt:$==null?void 0:$.dismissedAt,dismiss:G,restore:Y,isLoading:H,error:N,item:$}},me=()=>h.jsx("div",{className:"dismissible-loading","aria-live":"polite",children:"Loading..."}),ye=()=>h.jsx("div",{className:"dismissible-error",role:"alert",children:"Unable to load content. Please try again later."}),be=({onDismiss:t,ariaLabel:r})=>h.jsx("button",{className:"dismissible-button",onClick:t,"aria-label":r,type:"button",children:"×"}),we=({itemId:t,children:r,onDismiss:e,LoadingComponent:s=me,ErrorComponent:i=ye,DismissButtonComponent:n=be,enableCache:o,cachePrefix:c,cacheExpiration:y,ignoreErrors:S=!1})=>{const{dismissedAt:m,isLoading:I,error:d,dismiss:f}=L(t,{enableCache:o,cachePrefix:c,cacheExpiration:y}),[D,H]=a.useState(!1),[q,N]=a.useState(t);t!==q&&(N(t),H(!1));const v=()=>j(null,null,function*(){H(!0);try{yield f(),e==null||e()}catch($){H(!1)}});return I&&s?h.jsx(s,{itemId:t}):I&&!s?null:d&&i&&!S?h.jsx(i,{itemId:t,error:d}):m||D?null:h.jsxs("div",{className:"dismissible-container",children:[h.jsx("div",{className:"dismissible-content",children:r}),n?h.jsx(n,{onDismiss:v,ariaLabel:`Dismiss ${t}`}):null]})},te=t=>j(null,null,function*(){if(typeof t=="function")try{const r=t();return yield Promise.resolve(r)}catch(r){console.warn("Failed to resolve JWT from function:",r);return}return t}),re=t=>j(null,null,function*(){const r=yield te(t);return r?{Authorization:`Bearer ${r}`}:{}}),pe=/\{[^{}]+\}/g,ge=()=>{var t,r;return typeof process=="object"&&Number.parseInt((r=(t=process==null?void 0:process.versions)==null?void 0:t.node)==null?void 0:r.substring(0,2))>=18&&process.versions.undici};function ve(){return Math.random().toString(36).slice(2,11)}function Ce(t){let I=w({},t),{baseUrl:r="",Request:e=globalThis.Request,fetch:s=globalThis.fetch,querySerializer:i,bodySerializer:n,headers:o,requestInitExt:c=void 0}=I,y=ee(I,["baseUrl","Request","fetch","querySerializer","bodySerializer","headers","requestInitExt"]);c=ge()?c:void 0,r=ae(r);const S=[];function m(d,f){return j(this,null,function*(){var ue;const le=f||{},{baseUrl:D,fetch:H=s,Request:q=e,headers:N,params:v={},parseAs:$="json",querySerializer:T,bodySerializer:U=n!=null?n:Re,body:G,middleware:Y=[]}=le,u=ee(le,["baseUrl","fetch","Request","headers","params","parseAs","querySerializer","bodySerializer","body","middleware"]);let C=r;D&&(C=(ue=ae(D))!=null?ue:r);let p=typeof i=="function"?i:ie(i);T&&(p=typeof T=="function"?T:ie(w(w({},typeof i=="object"?i:{}),T)));const M=G===void 0?void 0:U(G,oe(o,N,v.header)),je=oe(M===void 0||M instanceof FormData?{}:{"Content-Type":"application/json"},o,N,v.header),P=[...S,...Y],xe=A(w(w({redirect:"follow"},y),u),{body:M,headers:je});let K,Q,O=new q(Se(d,{baseUrl:C,params:v,querySerializer:p}),xe),b;for(const E in u)E in O||(O[E]=u[E]);if(P.length){K=ve(),Q=Object.freeze({baseUrl:C,fetch:H,parseAs:$,querySerializer:p,bodySerializer:U});for(const E of P)if(E&&typeof E=="object"&&typeof E.onRequest=="function"){const g=yield E.onRequest({request:O,schemaPath:d,params:v,options:Q,id:K});if(g)if(g instanceof q)O=g;else if(g instanceof Response){b=g;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!b){try{b=yield H(O,c)}catch(E){let g=E;if(P.length)for(let k=P.length-1;k>=0;k--){const V=P[k];if(V&&typeof V=="object"&&typeof V.onError=="function"){const W=yield V.onError({request:O,error:g,schemaPath:d,params:v,options:Q,id:K});if(W){if(W instanceof Response){g=void 0,b=W;break}if(W instanceof Error){g=W;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(g)throw g}if(P.length)for(let E=P.length-1;E>=0;E--){const g=P[E];if(g&&typeof g=="object"&&typeof g.onResponse=="function"){const k=yield g.onResponse({request:O,response:b,schemaPath:d,params:v,options:Q,id:K});if(k){if(!(k instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");b=k}}}}if(b.status===204||O.method==="HEAD"||b.headers.get("Content-Length")==="0")return b.ok?{data:void 0,response:b}:{error:void 0,response:b};if(b.ok)return $==="stream"?{data:b.body,response:b}:{data:yield b[$](),response:b};let Z=yield b.text();try{Z=JSON.parse(Z)}catch(E){}return{error:Z,response:b}})}return{request(d,f,D){return m(f,A(w({},D),{method:d.toUpperCase()}))},GET(d,f){return m(d,A(w({},f),{method:"GET"}))},PUT(d,f){return m(d,A(w({},f),{method:"PUT"}))},POST(d,f){return m(d,A(w({},f),{method:"POST"}))},DELETE(d,f){return m(d,A(w({},f),{method:"DELETE"}))},OPTIONS(d,f){return m(d,A(w({},f),{method:"OPTIONS"}))},HEAD(d,f){return m(d,A(w({},f),{method:"HEAD"}))},PATCH(d,f){return m(d,A(w({},f),{method:"PATCH"}))},TRACE(d,f){return m(d,A(w({},f),{method:"TRACE"}))},use(...d){for(const f of d)if(f){if(typeof f!="object"||!("onRequest"in f||"onResponse"in f||"onError"in f))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");S.push(f)}},eject(...d){for(const f of d){const D=S.indexOf(f);D!==-1&&S.splice(D,1)}}}}function B(t,r,e){if(r==null)return"";if(typeof r=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${t}=${(e==null?void 0:e.allowReserved)===!0?r:encodeURIComponent(r)}`}function se(t,r,e){if(!r||typeof r!="object")return"";const s=[],i={simple:",",label:".",matrix:";"}[e.style]||"&";if(e.style!=="deepObject"&&e.explode===!1){for(const c in r)s.push(c,e.allowReserved===!0?r[c]:encodeURIComponent(r[c]));const o=s.join(",");switch(e.style){case"form":return`${t}=${o}`;case"label":return`.${o}`;case"matrix":return`;${t}=${o}`;default:return o}}for(const o in r){const c=e.style==="deepObject"?`${t}[${o}]`:o;s.push(B(c,r[o],e))}const n=s.join(i);return e.style==="label"||e.style==="matrix"?`${i}${n}`:n}function ne(t,r,e){if(!Array.isArray(r))return"";if(e.explode===!1){const n={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[e.style]||",",o=(e.allowReserved===!0?r:r.map(c=>encodeURIComponent(c))).join(n);switch(e.style){case"simple":return o;case"label":return`.${o}`;case"matrix":return`;${t}=${o}`;default:return`${t}=${o}`}}const s={simple:",",label:".",matrix:";"}[e.style]||"&",i=[];for(const n of r)e.style==="simple"||e.style==="label"?i.push(e.allowReserved===!0?n:encodeURIComponent(n)):i.push(B(t,n,e));return e.style==="label"||e.style==="matrix"?`${s}${i.join(s)}`:i.join(s)}function ie(t){return function(e){const s=[];if(e&&typeof e=="object")for(const i in e){const n=e[i];if(n!=null){if(Array.isArray(n)){if(n.length===0)continue;s.push(ne(i,n,A(w({style:"form",explode:!0},t==null?void 0:t.array),{allowReserved:(t==null?void 0:t.allowReserved)||!1})));continue}if(typeof n=="object"){s.push(se(i,n,A(w({style:"deepObject",explode:!0},t==null?void 0:t.object),{allowReserved:(t==null?void 0:t.allowReserved)||!1})));continue}s.push(B(i,n,t))}}return s.join("&")}}function Ee(t,r){var s;let e=t;for(const i of(s=t.match(pe))!=null?s:[]){let n=i.substring(1,i.length-1),o=!1,c="simple";if(n.endsWith("*")&&(o=!0,n=n.substring(0,n.length-1)),n.startsWith(".")?(c="label",n=n.substring(1)):n.startsWith(";")&&(c="matrix",n=n.substring(1)),!r||r[n]===void 0||r[n]===null)continue;const y=r[n];if(Array.isArray(y)){e=e.replace(i,ne(n,y,{style:c,explode:o}));continue}if(typeof y=="object"){e=e.replace(i,se(n,y,{style:c,explode:o}));continue}if(c==="matrix"){e=e.replace(i,`;${B(n,y)}`);continue}e=e.replace(i,c==="label"?`.${encodeURIComponent(y)}`:encodeURIComponent(y))}return e}function Re(t,r){var e,s;return t instanceof FormData?t:r&&(r.get instanceof Function?(e=r.get("Content-Type"))!=null?e:r.get("content-type"):(s=r["Content-Type"])!=null?s:r["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(t).toString():JSON.stringify(t)}function Se(t,r){var i,n;let e=`${r.baseUrl}${t}`;(i=r.params)!=null&&i.path&&(e=Ee(e,r.params.path));let s=r.querySerializer((n=r.params.query)!=null?n:{});return s.startsWith("?")&&(s=s.substring(1)),s&&(e+=`?${s}`),e}function oe(...t){const r=new Headers;for(const e of t){if(!e||typeof e!="object")continue;const s=e instanceof Headers?e.entries():Object.entries(e);for(const[i,n]of s)if(n===null)r.delete(i);else if(Array.isArray(n))for(const o of n)r.append(i,o);else n!==void 0&&r.set(i,n)}return r}function ae(t){return t.endsWith("/")?t.substring(0,t.length-1):t}const ce=t=>{const r=Ce({baseUrl:t,headers:{}});return{getOrCreate:e=>j(null,null,function*(){const{userId:s,itemId:i,authHeaders:n,signal:o}=e,{data:c,error:y}=yield r.GET("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:s,itemId:i}},headers:n,signal:o});if(y||!c)throw new Error("Failed to fetch dismissible item");return c.data}),dismiss:e=>j(null,null,function*(){const{userId:s,itemId:i,authHeaders:n}=e,{data:o,error:c}=yield r.DELETE("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:s,itemId:i}},headers:n});if(c||!o)throw new Error("Failed to dismiss item");return o.data}),restore:e=>j(null,null,function*(){const{userId:s,itemId:i,authHeaders:n}=e,{data:o,error:c}=yield r.POST("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:s,itemId:i}},headers:n});if(c||!o)throw new Error("Failed to restore item");return o.data})}},De=t=>{try{const r=new URL(t),e=r.hostname==="localhost"||r.hostname==="127.0.0.1"||r.hostname==="[::1]",s=r.protocol==="https:";return{isSecure:s||e,isLocalhost:e,isHttps:s}}catch(r){return{isSecure:!1,isLocalhost:!1,isHttps:!1}}},Ae=({userId:t,jwt:r,baseUrl:e,client:s,children:i})=>{const{isSecure:n}=De(e);n||console.warn(`[dismissible] Insecure baseUrl "${e}". Use https:// in production (or localhost for development). JWT tokens may be exposed over insecure connections.`);const o=a.useMemo(()=>s!=null?s:ce(e),[s,e]),c=a.useMemo(()=>({userId:t,jwt:r,baseUrl:e,getAuthHeaders:()=>j(null,null,function*(){return yield re(r)}),client:o}),[t,r,e,o]);return h.jsx(J.Provider,{value:c,children:i})};l.Dismissible=we,l.DismissibleContext=J,l.DismissibleProvider=Ae,l.createDefaultClient=ce,l.getAuthHeaders=re,l.resolveJwt=te,l.useDismissibleContext=F,l.useDismissibleItem=L,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(l,d){typeof exports=="object"&&typeof module!="undefined"?d(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],d):(l=typeof globalThis!="undefined"?globalThis:l||self,d(l.DismissibleClient={},l.React.jsxRuntime,l.React))})(this,(function(l,d,a){"use strict";var $e=Object.defineProperty,Te=Object.defineProperties;var He=Object.getOwnPropertyDescriptors;var Y=Object.getOwnPropertySymbols;var me=Object.prototype.hasOwnProperty,ye=Object.prototype.propertyIsEnumerable;var te=(l,d,a)=>d in l?$e(l,d,{enumerable:!0,configurable:!0,writable:!0,value:a}):l[d]=a,v=(l,d)=>{for(var a in d||(d={}))me.call(d,a)&&te(l,a,d[a]);if(Y)for(var a of Y(d))ye.call(d,a)&&te(l,a,d[a]);return l},q=(l,d)=>Te(l,He(d));var re=(l,d)=>{var a={};for(var S in l)me.call(l,S)&&d.indexOf(S)<0&&(a[S]=l[S]);if(l!=null&&Y)for(var S of Y(l))d.indexOf(S)<0&&ye.call(l,S)&&(a[S]=l[S]);return a};var N=(l,d,a)=>te(l,typeof d!="symbol"?d+"":d,a);var I=(l,d,a)=>new Promise((S,k)=>{var G=$=>{try{M(a.next($))}catch(L){k(L)}},J=$=>{try{M(a.throw($))}catch(L){k(L)}},M=$=>$.done?S($.value):Promise.resolve($.value).then(G,J);M((a=a.apply(l,d)).next())});const S=(r,t,e)=>{try{const n=`${t}_${r}`,i=localStorage.getItem(n);if(!i)return null;const{data:s,timestamp:o}=JSON.parse(i);return e&&Date.now()-o>e?(localStorage.removeItem(n),null):s}catch(n){return null}},k=(r,t,e)=>{try{const n=`${e}_${r}`,i={data:t,timestamp:Date.now()};localStorage.setItem(n,JSON.stringify(i))}catch(n){console.warn("Failed to cache dismissible item:",n)}},G=(r,t)=>{try{const e=`${t}_${r}`;localStorage.removeItem(e)}catch(e){console.warn("Failed to remove cached dismissible item:",e)}},J=a.createContext(null),M=()=>{const r=a.useContext(J);if(!r)throw new Error("useDismissibleContext must be used within a DismissibleProvider");return r},$="dismissible",L=(r,t={})=>{const{initialData:e,enableCache:n=!0,cachePrefix:i=$,cacheExpiration:s}=t,{userId:o,client:c,baseUrl:m,getAuthHeaders:b,batchScheduler:p}=M(),y=`${o}-${r}`,u=a.useRef({enableCache:n,cachePrefix:i,cacheExpiration:s}),h=a.useRef(r),x=a.useRef(y),A=a.useRef(null),[B,H]=a.useState(!1),[D,j]=a.useState(),[T,U]=a.useState(()=>{if(e)return e;if(n){const f=S(y,i,s);if(f)return f}}),P=a.useCallback(()=>I(null,null,function*(){var C;if(n){const w=S(y,i,s);if(w!=null&&w.dismissedAt){p.primeCache(w),U(w),H(!1);return}}(C=A.current)==null||C.abort();const f=new AbortController;A.current=f,H(!0),j(void 0);try{const w=yield p.getItem(r);if(f.signal.aborted)return;U(w),n&&k(y,w,i)}catch(w){if(w instanceof Error&&w.name==="AbortError"||f.signal.aborted)return;j(w instanceof Error?w:new Error("Unknown error occurred"))}finally{f.signal.aborted||H(!1)}}),[r,y,n,i,s,p]);a.useEffect(()=>{const f=h.current!==r,C=x.current!==y;f||C?(h.current=r,x.current=y,P()):e||P()},[r,y,e,P]),a.useEffect(()=>()=>{var f;(f=A.current)==null||f.abort()},[]),a.useEffect(()=>{const f=u.current;(f.enableCache!==n||f.cachePrefix!==i||f.cacheExpiration!==s)&&(f.cachePrefix!==i&&G(y,f.cachePrefix),!n&&f.enableCache&&G(y,f.cachePrefix),u.current={enableCache:n,cachePrefix:i,cacheExpiration:s},P())},[n,i,s,y,P]);const Z=a.useCallback(()=>I(null,null,function*(){j(void 0);try{const f=yield b(),C=yield c.dismiss({userId:o,itemId:r,baseUrl:m,authHeaders:f});U(C),p.updateCache(C),n&&k(y,C,i)}catch(f){throw j(f instanceof Error?f:new Error("Failed to dismiss item")),f}}),[r,o,y,n,i,c,m,b,p]),W=a.useCallback(()=>I(null,null,function*(){j(void 0);try{const f=yield b(),C=yield c.restore({userId:o,itemId:r,baseUrl:m,authHeaders:f});U(C),p.updateCache(C),n&&k(y,C,i)}catch(f){throw j(f instanceof Error?f:new Error("Failed to restore item")),f}}),[r,o,y,n,i,c,m,b,p]);return{dismissedAt:T==null?void 0:T.dismissedAt,dismiss:Z,restore:W,isLoading:B,error:D,item:T}},be=()=>d.jsx("div",{className:"dismissible-loading","aria-live":"polite",children:"Loading..."}),pe=()=>d.jsx("div",{className:"dismissible-error",role:"alert",children:"Unable to load content. Please try again later."}),we=({onDismiss:r,ariaLabel:t})=>d.jsx("button",{className:"dismissible-button",onClick:r,"aria-label":t,type:"button",children:"×"}),ge=({itemId:r,children:t,onDismiss:e,LoadingComponent:n=be,ErrorComponent:i=pe,DismissButtonComponent:s=we,enableCache:o,cachePrefix:c,cacheExpiration:m,ignoreErrors:b=!1})=>{const{dismissedAt:p,isLoading:y,error:u,dismiss:h}=L(r,{enableCache:o,cachePrefix:c,cacheExpiration:m}),[x,A]=a.useState(!1),[B,H]=a.useState(r);r!==B&&(H(r),A(!1));const D=()=>I(null,null,function*(){A(!0);try{yield h(),e==null||e()}catch(j){A(!1)}});return y&&n?d.jsx(n,{itemId:r}):y&&!n?null:u&&i&&!b?d.jsx(i,{itemId:r,error:u}):p||x?null:d.jsxs("div",{className:"dismissible-container",children:[d.jsx("div",{className:"dismissible-content",children:t}),s?d.jsx(s,{onDismiss:D,ariaLabel:`Dismiss ${r}`}):null]})},se=r=>I(null,null,function*(){if(typeof r=="function")try{const t=r();return yield Promise.resolve(t)}catch(t){console.warn("Failed to resolve JWT from function:",t);return}return r}),ne=r=>I(null,null,function*(){const t=yield se(r);return t?{Authorization:`Bearer ${t}`}:{}}),ve=/\{[^{}]+\}/g,Ce=()=>{var r,t;return typeof process=="object"&&Number.parseInt((t=(r=process==null?void 0:process.versions)==null?void 0:r.node)==null?void 0:t.substring(0,2))>=18&&process.versions.undici};function Re(){return Math.random().toString(36).slice(2,11)}function Ee(r){let y=v({},r),{baseUrl:t="",Request:e=globalThis.Request,fetch:n=globalThis.fetch,querySerializer:i,bodySerializer:s,headers:o,requestInitExt:c=void 0}=y,m=re(y,["baseUrl","Request","fetch","querySerializer","bodySerializer","headers","requestInitExt"]);c=Ce()?c:void 0,t=le(t);const b=[];function p(u,h){return I(this,null,function*(){var he;const de=h||{},{baseUrl:x,fetch:A=n,Request:B=e,headers:H,params:D={},parseAs:j="json",querySerializer:T,bodySerializer:U=s!=null?s:qe,body:P,middleware:Z=[]}=de,W=re(de,["baseUrl","fetch","Request","headers","params","parseAs","querySerializer","bodySerializer","body","middleware"]);let f=t;x&&(f=(he=le(x))!=null?he:t);let C=typeof i=="function"?i:ae(i);T&&(C=typeof T=="function"?T:ae(v(v({},typeof i=="object"?i:{}),T)));const w=P===void 0?void 0:U(P,ce(o,H,D.header)),Ae=ce(w===void 0||w instanceof FormData?{}:{"Content-Type":"application/json"},o,H,D.header),O=[...b,...Z],De=q(v(v({redirect:"follow"},m),W),{body:w,headers:Ae});let Q,V,F=new B(Ie(u,{baseUrl:f,params:D,querySerializer:C}),De),g;for(const E in W)E in F||(F[E]=W[E]);if(O.length){Q=Re(),V=Object.freeze({baseUrl:f,fetch:A,parseAs:j,querySerializer:C,bodySerializer:U});for(const E of O)if(E&&typeof E=="object"&&typeof E.onRequest=="function"){const R=yield E.onRequest({request:F,schemaPath:u,params:D,options:V,id:Q});if(R)if(R instanceof B)F=R;else if(R instanceof Response){g=R;break}else throw new Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!g){try{g=yield A(F,c)}catch(E){let R=E;if(O.length)for(let z=O.length-1;z>=0;z--){const X=O[z];if(X&&typeof X=="object"&&typeof X.onError=="function"){const _=yield X.onError({request:F,error:R,schemaPath:u,params:D,options:V,id:Q});if(_){if(_ instanceof Response){R=void 0,g=_;break}if(_ instanceof Error){R=_;continue}throw new Error("onError: must return new Response() or instance of Error")}}}if(R)throw R}if(O.length)for(let E=O.length-1;E>=0;E--){const R=O[E];if(R&&typeof R=="object"&&typeof R.onResponse=="function"){const z=yield R.onResponse({request:F,response:g,schemaPath:u,params:D,options:V,id:Q});if(z){if(!(z instanceof Response))throw new Error("onResponse: must return new Response() when modifying the response");g=z}}}}if(g.status===204||F.method==="HEAD"||g.headers.get("Content-Length")==="0")return g.ok?{data:void 0,response:g}:{error:void 0,response:g};if(g.ok)return j==="stream"?{data:g.body,response:g}:{data:yield g[j](),response:g};let ee=yield g.text();try{ee=JSON.parse(ee)}catch(E){}return{error:ee,response:g}})}return{request(u,h,x){return p(h,q(v({},x),{method:u.toUpperCase()}))},GET(u,h){return p(u,q(v({},h),{method:"GET"}))},PUT(u,h){return p(u,q(v({},h),{method:"PUT"}))},POST(u,h){return p(u,q(v({},h),{method:"POST"}))},DELETE(u,h){return p(u,q(v({},h),{method:"DELETE"}))},OPTIONS(u,h){return p(u,q(v({},h),{method:"OPTIONS"}))},HEAD(u,h){return p(u,q(v({},h),{method:"HEAD"}))},PATCH(u,h){return p(u,q(v({},h),{method:"PATCH"}))},TRACE(u,h){return p(u,q(v({},h),{method:"TRACE"}))},use(...u){for(const h of u)if(h){if(typeof h!="object"||!("onRequest"in h||"onResponse"in h||"onError"in h))throw new Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");b.push(h)}},eject(...u){for(const h of u){const x=b.indexOf(h);x!==-1&&b.splice(x,1)}}}}function K(r,t,e){if(t==null)return"";if(typeof t=="object")throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${r}=${(e==null?void 0:e.allowReserved)===!0?t:encodeURIComponent(t)}`}function ie(r,t,e){if(!t||typeof t!="object")return"";const n=[],i={simple:",",label:".",matrix:";"}[e.style]||"&";if(e.style!=="deepObject"&&e.explode===!1){for(const c in t)n.push(c,e.allowReserved===!0?t[c]:encodeURIComponent(t[c]));const o=n.join(",");switch(e.style){case"form":return`${r}=${o}`;case"label":return`.${o}`;case"matrix":return`;${r}=${o}`;default:return o}}for(const o in t){const c=e.style==="deepObject"?`${r}[${o}]`:o;n.push(K(c,t[o],e))}const s=n.join(i);return e.style==="label"||e.style==="matrix"?`${i}${s}`:s}function oe(r,t,e){if(!Array.isArray(t))return"";if(e.explode===!1){const s={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[e.style]||",",o=(e.allowReserved===!0?t:t.map(c=>encodeURIComponent(c))).join(s);switch(e.style){case"simple":return o;case"label":return`.${o}`;case"matrix":return`;${r}=${o}`;default:return`${r}=${o}`}}const n={simple:",",label:".",matrix:";"}[e.style]||"&",i=[];for(const s of t)e.style==="simple"||e.style==="label"?i.push(e.allowReserved===!0?s:encodeURIComponent(s)):i.push(K(r,s,e));return e.style==="label"||e.style==="matrix"?`${n}${i.join(n)}`:i.join(n)}function ae(r){return function(e){const n=[];if(e&&typeof e=="object")for(const i in e){const s=e[i];if(s!=null){if(Array.isArray(s)){if(s.length===0)continue;n.push(oe(i,s,q(v({style:"form",explode:!0},r==null?void 0:r.array),{allowReserved:(r==null?void 0:r.allowReserved)||!1})));continue}if(typeof s=="object"){n.push(ie(i,s,q(v({style:"deepObject",explode:!0},r==null?void 0:r.object),{allowReserved:(r==null?void 0:r.allowReserved)||!1})));continue}n.push(K(i,s,r))}}return n.join("&")}}function Se(r,t){var n;let e=r;for(const i of(n=r.match(ve))!=null?n:[]){let s=i.substring(1,i.length-1),o=!1,c="simple";if(s.endsWith("*")&&(o=!0,s=s.substring(0,s.length-1)),s.startsWith(".")?(c="label",s=s.substring(1)):s.startsWith(";")&&(c="matrix",s=s.substring(1)),!t||t[s]===void 0||t[s]===null)continue;const m=t[s];if(Array.isArray(m)){e=e.replace(i,oe(s,m,{style:c,explode:o}));continue}if(typeof m=="object"){e=e.replace(i,ie(s,m,{style:c,explode:o}));continue}if(c==="matrix"){e=e.replace(i,`;${K(s,m)}`);continue}e=e.replace(i,c==="label"?`.${encodeURIComponent(m)}`:encodeURIComponent(m))}return e}function qe(r,t){var e,n;return r instanceof FormData?r:t&&(t.get instanceof Function?(e=t.get("Content-Type"))!=null?e:t.get("content-type"):(n=t["Content-Type"])!=null?n:t["content-type"])==="application/x-www-form-urlencoded"?new URLSearchParams(r).toString():JSON.stringify(r)}function Ie(r,t){var i,s;let e=`${t.baseUrl}${r}`;(i=t.params)!=null&&i.path&&(e=Se(e,t.params.path));let n=t.querySerializer((s=t.params.query)!=null?s:{});return n.startsWith("?")&&(n=n.substring(1)),n&&(e+=`?${n}`),e}function ce(...r){const t=new Headers;for(const e of r){if(!e||typeof e!="object")continue;const n=e instanceof Headers?e.entries():Object.entries(e);for(const[i,s]of n)if(s===null)t.delete(i);else if(Array.isArray(s))for(const o of s)t.append(i,o);else s!==void 0&&t.set(i,s)}return t}function le(r){return r.endsWith("/")?r.substring(0,r.length-1):r}const ue=r=>{const t=Ee({baseUrl:r,headers:{}});return{getOrCreate:e=>I(null,null,function*(){const{userId:n,itemId:i,authHeaders:s,signal:o}=e,{data:c,error:m}=yield t.GET("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:n,itemId:i}},headers:s,signal:o});if(m||!c)throw new Error("Failed to fetch dismissible item");return c.data}),batchGetOrCreate:e=>I(null,null,function*(){const{userId:n,itemIds:i,authHeaders:s,signal:o}=e,{data:c,error:m}=yield t.POST("/v1/users/{userId}/items",{params:{path:{userId:n}},body:{items:i},headers:s,signal:o});if(m||!c)throw new Error("Failed to batch fetch dismissible items");return c.data}),dismiss:e=>I(null,null,function*(){const{userId:n,itemId:i,authHeaders:s}=e,{data:o,error:c}=yield t.DELETE("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:n,itemId:i}},headers:s});if(c||!o)throw new Error("Failed to dismiss item");return o.data}),restore:e=>I(null,null,function*(){const{userId:n,itemId:i,authHeaders:s}=e,{data:o,error:c}=yield t.POST("/v1/users/{userId}/items/{itemId}",{params:{path:{userId:n,itemId:i}},headers:s});if(c||!o)throw new Error("Failed to restore item");return o.data})}},xe=r=>{try{const t=new URL(r),e=t.hostname==="localhost"||t.hostname==="127.0.0.1"||t.hostname==="[::1]",n=t.protocol==="https:";return{isSecure:n||e,isLocalhost:e,isHttps:n}}catch(t){return{isSecure:!1,isLocalhost:!1,isHttps:!1}}};class fe{constructor(t){N(this,"config");N(this,"pendingRequests",[]);N(this,"isScheduled",!1);N(this,"cache",new Map);N(this,"inFlightRequests",new Map);var e;this.config=q(v({},t),{maxBatchSize:(e=t.maxBatchSize)!=null?e:50})}getItem(t){const e=this.cache.get(t);if(e)return Promise.resolve(e);const n=this.inFlightRequests.get(t);if(n)return n;let i,s;const o=new Promise((c,m)=>{i=c,s=m});return this.pendingRequests.push({itemId:t,resolve:i,reject:s}),this.inFlightRequests.set(t,o),o.catch(()=>{}).finally(()=>{this.inFlightRequests.delete(t)}),this.isScheduled||(this.isScheduled=!0,queueMicrotask(()=>this.executeBatch())),o}primeCache(t){this.cache.set(t.itemId,t)}updateCache(t){this.cache.set(t.itemId,t)}clearCache(){this.cache.clear()}executeBatch(){return I(this,null,function*(){this.isScheduled=!1;const t=this.pendingRequests;if(this.pendingRequests=[],t.length===0)return;const e=new Map;for(const s of t){const o=e.get(s.itemId);o?o.push(s):e.set(s.itemId,[s])}const n=Array.from(e.keys()),i=[];for(let s=0;s<n.length;s+=this.config.maxBatchSize)i.push(n.slice(s,s+this.config.maxBatchSize));try{const s=yield this.config.getAuthHeaders(),c=(yield Promise.all(i.map(b=>this.config.client.batchGetOrCreate({userId:this.config.userId,itemIds:b,baseUrl:this.config.baseUrl,authHeaders:s})))).flat(),m=new Map;for(const b of c)m.set(b.itemId,b),this.cache.set(b.itemId,b);for(const[b,p]of e){const y=m.get(b);if(y)for(const u of p)u.resolve(y);else{const u=new Error(`Item ${b} not found in batch response`);for(const h of p)h.reject(u)}}}catch(s){const o=s instanceof Error?s:new Error("Batch request failed");for(const c of t)c.reject(o)}})}}const je=({userId:r,jwt:t,baseUrl:e,client:n,children:i})=>{a.useEffect(()=>{const{isSecure:b}=xe(e);b||console.warn(`[dismissible] Insecure baseUrl "${e}". Use https:// in production (or localhost for development). JWT tokens may be exposed over insecure connections.`)},[e]);const s=a.useMemo(()=>n!=null?n:ue(e),[n,e]),o=a.useMemo(()=>()=>I(null,null,function*(){return yield ne(t)}),[t]),c=a.useMemo(()=>new fe({userId:r,baseUrl:e,client:s,getAuthHeaders:o}),[r,e,s,o]),m=a.useMemo(()=>({userId:r,jwt:t,baseUrl:e,getAuthHeaders:o,client:s,batchScheduler:c}),[r,t,e,o,s,c]);return d.jsx(J.Provider,{value:m,children:i})};l.BatchScheduler=fe,l.Dismissible=ge,l.DismissibleContext=J,l.DismissibleProvider=je,l.createDefaultClient=ue,l.getAuthHeaders=ne,l.resolveJwt=se,l.useDismissibleContext=M,l.useDismissibleItem=L,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
package/dist/root.d.ts CHANGED
@@ -5,3 +5,4 @@ export * from './contexts/DismissibleProvider';
5
5
  export * from './types/dismissible.types';
6
6
  export * from './utils/auth.utils';
7
7
  export * from './clients/defaultClient';
8
+ export * from './utils/BatchScheduler';
@@ -53,6 +53,21 @@ export type DismissClientParams = BaseDismissibleClientParams;
53
53
  * Parameters for the restore client method
54
54
  */
55
55
  export type RestoreClientParams = BaseDismissibleClientParams;
56
+ /**
57
+ * Parameters for the batchGetOrCreate client method
58
+ */
59
+ export interface BatchGetOrCreateClientParams {
60
+ /** User ID for the current user */
61
+ userId: string;
62
+ /** Array of dismissible item IDs to fetch (max 50) */
63
+ itemIds: string[];
64
+ /** Base URL for API requests */
65
+ baseUrl: string;
66
+ /** Authentication headers */
67
+ authHeaders: AuthHeaders;
68
+ /** AbortSignal for request cancellation */
69
+ signal?: AbortSignal;
70
+ }
56
71
  /**
57
72
  * Interface for custom HTTP clients
58
73
  *
@@ -77,6 +92,8 @@ export type RestoreClientParams = BaseDismissibleClientParams;
77
92
  export interface DismissibleClient {
78
93
  /** Get or create a dismissible item */
79
94
  getOrCreate: (params: GetOrCreateClientParams) => Promise<DismissibleItem>;
95
+ /** Batch get or create multiple dismissible items (max 50) */
96
+ batchGetOrCreate: (params: BatchGetOrCreateClientParams) => Promise<DismissibleItem[]>;
80
97
  /** Dismiss an item */
81
98
  dismiss: (params: DismissClientParams) => Promise<DismissibleItem>;
82
99
  /** Restore a dismissed item */
@@ -97,6 +114,19 @@ export interface DismissibleProviderProps {
97
114
  /** Child components */
98
115
  children: React.ReactNode;
99
116
  }
117
+ /**
118
+ * Batch scheduler interface for request coalescing
119
+ */
120
+ export interface IBatchScheduler {
121
+ /** Request a dismissible item (batched with other requests in same tick) */
122
+ getItem: (itemId: string) => Promise<DismissibleItem>;
123
+ /** Pre-populate cache with an item */
124
+ primeCache: (item: DismissibleItem) => void;
125
+ /** Update an item in the cache */
126
+ updateCache: (item: DismissibleItem) => void;
127
+ /** Clear the in-memory cache */
128
+ clearCache: () => void;
129
+ }
100
130
  /**
101
131
  * Context value provided by DismissibleProvider
102
132
  */
@@ -111,4 +141,6 @@ export interface DismissibleContextValue {
111
141
  getAuthHeaders: () => Promise<AuthHeaders>;
112
142
  /** The HTTP client to use for API requests */
113
143
  client: DismissibleClient;
144
+ /** Batch scheduler for coalescing getOrCreate requests */
145
+ batchScheduler: IBatchScheduler;
114
146
  }
@@ -0,0 +1,72 @@
1
+ import { DismissibleClient, DismissibleItem, AuthHeaders } from '../types/dismissible.types';
2
+ /**
3
+ * Configuration for the BatchScheduler
4
+ */
5
+ export interface BatchSchedulerConfig {
6
+ /** User ID for API requests */
7
+ userId: string;
8
+ /** Base URL for API requests */
9
+ baseUrl: string;
10
+ /** The HTTP client to use */
11
+ client: DismissibleClient;
12
+ /** Function to get auth headers */
13
+ getAuthHeaders: () => Promise<AuthHeaders>;
14
+ /** Maximum items per batch (API limit is 50) */
15
+ maxBatchSize?: number;
16
+ }
17
+ /**
18
+ * BatchScheduler implements DataLoader-style request batching.
19
+ *
20
+ * When multiple components request items in the same JavaScript tick,
21
+ * the scheduler coalesces them into a single batch API call using
22
+ * queueMicrotask to defer execution until after synchronous code completes.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const scheduler = new BatchScheduler({
27
+ * userId: "user-123",
28
+ * baseUrl: "https://api.example.com",
29
+ * client: dismissibleClient,
30
+ * getAuthHeaders: async () => ({ Authorization: "Bearer ..." }),
31
+ * });
32
+ *
33
+ * // These three calls in the same tick will be batched into one API call
34
+ * const [item1, item2, item3] = await Promise.all([
35
+ * scheduler.getItem("item-1"),
36
+ * scheduler.getItem("item-2"),
37
+ * scheduler.getItem("item-3"),
38
+ * ]);
39
+ * ```
40
+ */
41
+ export declare class BatchScheduler {
42
+ private config;
43
+ private pendingRequests;
44
+ private isScheduled;
45
+ private cache;
46
+ private inFlightRequests;
47
+ constructor(config: BatchSchedulerConfig);
48
+ /**
49
+ * Request a dismissible item. If called multiple times in the same
50
+ * JavaScript tick, requests will be batched into a single API call.
51
+ *
52
+ * @param itemId - The item ID to fetch
53
+ * @returns Promise resolving to the DismissibleItem
54
+ */
55
+ getItem(itemId: string): Promise<DismissibleItem>;
56
+ /**
57
+ * Pre-populate the cache with items (e.g., from localStorage)
58
+ */
59
+ primeCache(item: DismissibleItem): void;
60
+ /**
61
+ * Update an item in the cache (e.g., after dismiss/restore)
62
+ */
63
+ updateCache(item: DismissibleItem): void;
64
+ /**
65
+ * Clear the in-memory cache
66
+ */
67
+ clearCache(): void;
68
+ /**
69
+ * Execute the batched requests
70
+ */
71
+ private executeBatch;
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dismissible/react-client",
3
- "version": "2.0.0-canary.5.88c27fb",
3
+ "version": "2.1.0-canary.6.0a7a428",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",