shared-reducer 5.0.1 → 5.0.3

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.
@@ -10,15 +10,16 @@ interface Dispatch<T, SpecT> {
10
10
  (specs: DispatchSpec<T, SpecT>, syncedCallback?: (state: T) => void, errorCallback?: (error: string) => void): void;
11
11
  }
12
12
 
13
+ type MaybePromise<T> = Promise<T> | T;
14
+
15
+ type Handler = (signal: AbortSignal) => MaybePromise<void>;
16
+ type ErrorHandler = (e: unknown) => void;
13
17
  interface Scheduler {
14
- trigger(fn: (signal: AbortSignal) => Promise<void>): void;
15
- schedule(fn: (signal: AbortSignal) => Promise<void>): void;
18
+ trigger(fn: Handler, errorHandler: ErrorHandler): void;
19
+ schedule(fn: Handler, errorHandler: ErrorHandler): void;
16
20
  stop(): void;
17
21
  }
18
22
 
19
- type MaybePromise<T> = Promise<T> | T;
20
-
21
- type Handler = (signal: AbortSignal) => MaybePromise<void>;
22
23
  type DelayGetter = (attempt: number) => number;
23
24
  declare class OnlineScheduler implements Scheduler {
24
25
  private readonly _delayGetter;
@@ -26,10 +27,11 @@ declare class OnlineScheduler implements Scheduler {
26
27
  private _timeout;
27
28
  private _stop;
28
29
  private _handler;
30
+ private _errorHandler;
29
31
  private _attempts;
30
32
  constructor(_delayGetter: DelayGetter, _connectTimeLimit: number);
31
- trigger(handler: Handler): void;
32
- schedule(handler: Handler): void;
33
+ trigger(handler: Handler, errorHandler: ErrorHandler): void;
34
+ schedule(handler: Handler, errorHandler: ErrorHandler): void;
33
35
  stop(): void;
34
36
  private _attempt;
35
37
  private _remove;
package/frontend/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";class t{t;i;h=null;o=null;l=null;u=0;constructor(t,s){this.t=t,this.i=s,this._=this._.bind(this)}trigger(t){this.stop(),this.l=t,this._()}schedule(t){this.l!==t&&(this.o&&this.stop(),this.l=t,null===this.h&&("hidden"===global.document?.visibilityState?global.addEventListener?.("visibilitychange",this._):(global.addEventListener?.("online",this._),this.h=setTimeout(this._,this.t(this.u))),global.addEventListener?.("pageshow",this._),global.addEventListener?.("focus",this._),++this.u))}stop(){this.l=null,this.o?.(),this.o=null,this.m()}async _(){if(this.o||!this.l)return;this.m();const t=new AbortController;let s=()=>null;this.o=()=>{s(),t.abort()};try{await Promise.race([this.l(t.signal),new Promise(((t,e)=>{const i=setTimeout(e,this.i);s=()=>clearTimeout(i)}))]),this.l=null,this.u=0}catch(s){if(!t.signal.aborted){t.abort();const s=this.l;s&&(this.l=null,this.schedule(s))}}finally{s(),this.o=null}}m(){null!==this.h&&(clearTimeout(this.h),this.h=null,global.removeEventListener?.("online",this._),global.removeEventListener?.("pageshow",this._),global.removeEventListener?.("visibilitychange",this._),global.removeEventListener?.("focus",this._))}}const s=({base:t=2,initialDelay:s,maxDelay:e,randomness:i=0})=>n=>Math.min(Math.pow(t,n)*s,e)*(1-Math.random()*i);function e(t,s,e){const i=[],n=[];function h(){if(n.length>0){const e=t.combine(n);i.push(e),s=t.update(s,e),n.length=0}}return function(t,s){let e={p:t,v:0,C:null};for(;e;)if(e.v>=e.p.length)e=e.C;else{const t=s(e.p[e.v]);++e.v,t&&t.length&&(e={p:t,v:0,C:e})}}(e,(t=>{if("function"==typeof t){h();return t(s)}return t&&n.push(t),null})),h(),{S:s,T:t.combine(i)}}class i extends EventTarget{addEventListener(t,s,e){super.addEventListener(t,s,e)}removeEventListener(t,s,e){super.removeEventListener(t,s,e)}dispatchEvent(t){return super.dispatchEvent(t)}}const n=(t,s)=>new CustomEvent(t,{detail:s});class h extends i{$;k;I=null;P=!1;constructor(t,s){super(),this.$=t,this.k=s,this.L=this.L.bind(this),this.k.trigger(this.L)}async L(t){const{url:s,token:e}=await this.$(t);t.throwIfAborted();const i=new AbortController,h=i.signal;await new Promise(((u,d)=>{const _=new WebSocket(s);let g=!0;const f=t=>{i.abort(),_.close(),g?(g=!1,this.dispatchEvent(n("connectionfailure",t)),d(new Error(`handshake failed: ${t.code} ${t.reason}`))):(this.I=null,this.dispatchEvent(n("disconnected",t)),this.P||this.k.schedule(this.L))};e&&_.addEventListener("open",(()=>_.send(e)),{once:!0,signal:h}),_.addEventListener("message",(t=>{t.data!==c&&(g&&(g=!1,this.I=_,this.dispatchEvent(n("connected")),u()),this.dispatchEvent(n("message",t.data)))}),{signal:h}),_.addEventListener("close",f,{signal:h}),_.addEventListener("error",(()=>f(o)),{signal:h}),t.addEventListener("abort",(()=>f(r)),{signal:h}),function(t){const s=new AbortController;let e=null;const i=()=>{null!==e&&(clearTimeout(e),e=null),t.send(l)},n=()=>{null!==e&&clearTimeout(e),e=setTimeout(i,a)},h=()=>{null!==e&&(clearTimeout(e),e=null),s.abort()};t.addEventListener("open",n,{once:!0,signal:s.signal}),t.addEventListener("message",n,{signal:s.signal}),t.addEventListener("close",h,{signal:s.signal}),t.addEventListener("error",h,{signal:s.signal}),global.addEventListener?.("offline",i,{signal:s.signal})}(_)})).catch((t=>{throw i.abort(),t}))}isConnected(){return null!==this.I}send=t=>{if(!this.I)throw new Error("connection lost");this.I.send(t)};close(){this.P=!0,this.k.stop(),this.I?.close()}}const o={code:0,reason:"client side error"},r={code:0,reason:"handshake timeout"},l="P",c="p",a=2e4,u=()=>!0;class d{M;A;O=[];j=function(){let t=1;return()=>t++}();constructor(t,s){this.M=t,this.A=s}D(t){this.O.push({J:void 0,N:t,F:[],G:[]})}q(t,s,e){if(s||e)if(this.O.length){const t=this.O[this.O.length-1];t.F.push(s??_),t.G.push(e??_)}else s&&Promise.resolve(t).then(s)}U(t){let s=0;for(let e=0;e<this.O.length;++e){const i=this.O[e];this.A(t,i.N,void 0!==i.J)?(i.J=void 0,this.O[e-s]=i):(i.G.forEach((t=>t("message possibly lost"))),++s)}this.O.length-=s}W(t){for(let t=0,s=0;t<=this.O.length;++t){const e=this.O[t];if(!e||void 0!==e.J||e.F.length>0||e.G.length>0){const e=t-s;if(e>1){const i=this.O[t-1],n=this.O.splice(s,e,i);i.N=this.M.combine(n.map((t=>t.N))),t-=e-1}s=t+1}}for(const s of this.O)void 0===s.J&&(s.J=this.j(),t(JSON.stringify({change:s.N,id:s.J})))}X(t){const s=void 0===t?-1:this.O.findIndex((s=>s.J===t));return-1===s?{B:null,H:!1}:{B:this.O.splice(s,1)[0],H:0===s}}K(t){if(!this.O.length)return t;const s=this.M.combine(this.O.map((({N:t})=>t)));return this.M.update(t,s)}}const _=()=>null;const g={code:0,reason:"graceful shutdown"},f=s({base:2,initialDelay:1e3,maxDelay:6e5,randomness:.3});exports.AT_LEAST_ONCE=u,exports.AT_MOST_ONCE=(t,s,e)=>!e,exports.OnlineScheduler=t,exports.SharedReducer=class extends i{M;I;R=!0;S={V:0,Y:[]};Z;tt=new Set;st=function(t){let s=!1;return e=>{if(s)throw new Error(t);try{return s=!0,e()}finally{s=!1}}}("Cannot dispatch recursively");constructor(s,e,{scheduler:i=new t(f,2e4),deliveryStrategy:n=u}={}){super(),this.M=s,this.Z=new d(s,n),this.I=new h(e,i),this.I.addEventListener("message",this.et),this.I.addEventListener("connected",this.it),this.I.addEventListener("connectionfailure",this.nt),this.I.addEventListener("disconnected",this.ht)}dispatch=function(t){return Object.assign(t,{sync:(s=[])=>new Promise(((e,i)=>t(s,e,(t=>i(new Error(t))))))})}(((t,s,e)=>{if(!t.length&&!s&&!e)return;const i={ot:t,F:s,G:e};switch(this.S.V){case-1:throw new Error("closed");case 0:this.S.Y.push(i);break;case 1:this.rt(this.lt(this.S.ct,[i])),this.dt.ut()}}));lt(t,s){return this.st((()=>{for(const{ot:i,F:n,G:h}of s){if(i.length){const{S:s,T:n}=e(this.M,t,i);t=s,this.Z.D(n)}this.Z.q(t,n,h)}return t}))}dt=function(t){let s=null;const e=()=>{null!==s&&(clearTimeout(s),s=null)},i=()=>{e(),t()};return{_t:i,ut:()=>{null===s&&(s=setTimeout(i,0))},o:e}}((()=>{this.I.isConnected()&&!this.R&&this.Z.W(this.I.send)}));gt(t){if(-1!==this.S.V){if(this.R=!1,0===this.S.V){const s=this.lt(t.init,this.S.Y);this.S={V:1,ft:t.init,ct:s},this.rt(s,!0)}else this.S.ft=t.init,this.Z.U(t.init),this.rt(this.Z.K(t.init));this.dt._t()}else this.wt(`Ignoring init after closing: ${JSON.stringify(t)}`)}vt(t){if(1!==this.S.V)return void this.wt(`Ignoring change before init: ${JSON.stringify(t)}`);const s=this.S.ft=this.M.update(this.S.ft,t.change),{B:e,H:i}=this.Z.X(t.id);i||this.rt(this.Z.K(s)),e?.F.forEach((t=>t(s)))}bt(t){if(1!==this.S.V)return void this.wt(`Ignoring error before init: ${JSON.stringify(t)}`);const{B:s}=this.Z.X(t.id);s?(this.wt(`API rejected update: ${t.error}`),s?.G.forEach((s=>s(t.error))),this.rt(this.Z.K(this.S.ft))):this.wt(`API sent error: ${t.error}`)}yt(){this.I.send("x"),this.R?this.wt("Unexpected extra close message"):(this.R=!0,this.dispatchEvent(n("disconnected",g)))}et=t=>{if("X"===t.detail)return void this.yt();const s=JSON.parse(t.detail);"change"in s?this.vt(s):"init"in s?this.gt(s):"error"in s?this.bt(s):this.wt(`Ignoring unknown API message: ${t.detail}`)};addStateListener(t){this.tt.add(t),1===this.S.V&&t(this.S.ct)}removeStateListener(t){this.tt.delete(t)}rt(t,s=!1){if(1!==this.S.V)throw new Error("invalid state");if(s||this.S.ct!==t){this.S.ct=t;for(const s of this.tt)s(t)}}getState(){return 1===this.S.V?this.S.ct:void 0}wt(t){this.dispatchEvent(n("warning",new Error(t)))}it=()=>{this.dispatchEvent(n("connected"))};nt=t=>{this.dispatchEvent(n("warning",new Error(`connection failure: ${t.detail.code} ${t.detail.reason}`)))};ht=t=>{this.R||(this.R=!0,this.dispatchEvent(n("disconnected",t.detail)))};close(){this.R=!0,this.S={V:-1},this.I.close(),this.dt.o(),this.tt.clear(),this.I.removeEventListener("message",this.et),this.I.removeEventListener("connected",this.it),this.I.removeEventListener("connectionfailure",this.nt),this.I.removeEventListener("disconnected",this.ht)}},exports.exponentialDelay=s;
1
+ "use strict";class t{t;i;h=null;o=null;l=null;u=()=>null;_=0;constructor(t,s){this.t=t,this.i=s,this.m=this.m.bind(this)}trigger(t,s){this.stop(),this.l=t,this.u=s,this.m()}schedule(t,s){this.l!==t&&(this.o&&this.stop(),this.l=t,this.u=s,null===this.h&&("hidden"===globalThis.document?.visibilityState?globalThis.addEventListener?.("visibilitychange",this.m):(globalThis.addEventListener?.("online",this.m),this.h=setTimeout(this.m,this.t(this._))),globalThis.addEventListener?.("pageshow",this.m),globalThis.addEventListener?.("focus",this.m),++this._))}stop(){this.l=null,this.o?.(),this.o=null,this.p()}async m(){if(this.o||!this.l)return;this.p();const t=new AbortController,s=(t=>{const s={stop:()=>{}};return s.promise=new Promise(((e,i)=>{const n=setTimeout((()=>i(new Error(`Timed out after ${t}ms`))),t);s.stop=()=>clearTimeout(n)})),s})(this.i);this.o=()=>{s.stop(),t.abort()};try{await Promise.race([this.l(t.signal),s.promise]),this.l=null,this._=0}catch(s){if(!t.signal.aborted){t.abort();try{this.u(s)}catch(t){console.error("Error handler failed",s,t)}const e=this.l;e&&(this.l=null,this.schedule(e,this.u))}}finally{s.stop(),this.o=null}}p(){null!==this.h&&(clearTimeout(this.h),this.h=null,globalThis.removeEventListener?.("online",this.m),globalThis.removeEventListener?.("pageshow",this.m),globalThis.removeEventListener?.("visibilitychange",this.m),globalThis.removeEventListener?.("focus",this.m))}}const s=({base:t=2,initialDelay:s,maxDelay:e,randomness:i=0})=>n=>Math.min(Math.pow(t,n)*s,e)*(1-Math.random()*i);function e(t,s,e){const i=[],n=[];function h(){if(n.length>0){const e=t.combine(n);i.push(e),s=t.update(s,e),n.length=0}}return function(t,s){let e={v:t,T:0,C:null};for(;e;)if(e.T>=e.v.length)e=e.C;else{const t=s(e.v[e.T]);++e.T,t&&t.length&&(e={v:t,T:0,C:e})}}(e,(t=>{if("function"==typeof t){h();return t(s)}return t&&n.push(t),null})),h(),{S:s,$:t.combine(i)}}class i extends EventTarget{addEventListener(t,s,e){super.addEventListener(t,s,e)}removeEventListener(t,s,e){super.removeEventListener(t,s,e)}dispatchEvent(t){return super.dispatchEvent(t)}}const n=(t,s)=>new CustomEvent(t,{detail:s});class h extends i{I;P;k=null;L=!1;constructor(t,s){super(),this.I=t,this.P=s,this.M=this.M.bind(this),this.A=this.A.bind(this),this.P.trigger(this.M,this.A)}A(t){const s=t instanceof Error?t:new Error(`unknown connection error ${t}`);this.dispatchEvent(n("connectionfailure",s))}async M(t){const{url:s,token:e}=await this.I(t);if(t.aborted)throw t.reason;const i=new AbortController,h=i.signal;await new Promise(((a,u)=>{const d=new WebSocket(s);let _=!0;const g=t=>{i.abort(),d.close(),_?(_=!1,u(new Error(`Connection closed ${t.code} ${t.reason}`))):(this.k=null,this.dispatchEvent(n("disconnected",t)),this.L||this.P.schedule(this.M,this.A))};e&&d.addEventListener("open",(()=>d.send(e)),{once:!0,signal:h}),d.addEventListener("message",(t=>{t.data!==l&&(_&&(_=!1,this.k=d,this.dispatchEvent(n("connected")),a()),this.dispatchEvent(n("message",t.data)))}),{signal:h}),d.addEventListener("close",g,{signal:h}),d.addEventListener("error",(()=>g(o)),{signal:h}),t.addEventListener("abort",(()=>()=>{i.abort(),d.close(),_=!1,u(t.reason)}),{signal:h}),function(t){const s=new AbortController;let e=null;const i=()=>{null!==e&&(clearTimeout(e),e=null),t.send(r)},n=()=>{null!==e&&clearTimeout(e),e=setTimeout(i,c)},h=()=>{null!==e&&(clearTimeout(e),e=null),s.abort()};t.addEventListener("open",n,{once:!0,signal:s.signal}),t.addEventListener("message",n,{signal:s.signal}),t.addEventListener("close",h,{signal:s.signal}),t.addEventListener("error",h,{signal:s.signal}),globalThis.addEventListener?.("offline",i,{signal:s.signal})}(d)})).catch((t=>{throw i.abort(),t}))}isConnected(){return null!==this.k}send=t=>{if(!this.k)throw new Error("connection lost");this.k.send(t)};close(){this.L=!0,this.P.stop(),this.k?.close()}}const o={code:0,reason:"client side error"},r="P",l="p",c=2e4,a=()=>!0;class u{O;j;D=[];J=function(){let t=1;return()=>t++}();constructor(t,s){this.O=t,this.j=s}N(t){this.D.push({F:void 0,G:t,q:[],H:[]})}U(t,s,e){if(s||e)if(this.D.length){const t=this.D[this.D.length-1];t.q.push(s??d),t.H.push(e??d)}else s&&Promise.resolve(t).then(s)}W(t){let s=0;for(let e=0;e<this.D.length;++e){const i=this.D[e];this.j(t,i.G,void 0!==i.F)?(i.F=void 0,this.D[e-s]=i):(i.H.forEach((t=>t("message possibly lost"))),++s)}this.D.length-=s}X(t){for(let t=0,s=0;t<=this.D.length;++t){const e=this.D[t];if(!e||void 0!==e.F||e.q.length>0||e.H.length>0){const e=t-s;if(e>1){const i=this.D[t-1],n=this.D.splice(s,e,i);i.G=this.O.combine(n.map((t=>t.G))),t-=e-1}s=t+1}}for(const s of this.D)void 0===s.F&&(s.F=this.J(),t(JSON.stringify({change:s.G,id:s.F})))}B(t){const s=void 0===t?-1:this.D.findIndex((s=>s.F===t));return-1===s?{K:null,R:!1}:{K:this.D.splice(s,1)[0],R:0===s}}V(t){if(!this.D.length)return t;const s=this.O.combine(this.D.map((({G:t})=>t)));return this.O.update(t,s)}}const d=()=>null;const _={code:0,reason:"graceful shutdown"},g=s({base:2,initialDelay:1e3,maxDelay:6e5,randomness:.3});exports.AT_LEAST_ONCE=a,exports.AT_MOST_ONCE=(t,s,e)=>!e,exports.OnlineScheduler=t,exports.SharedReducer=class extends i{O;k;Y=!0;S={Z:0,tt:[]};st;et=new Set;it=function(t){let s=!1;return e=>{if(s)throw new Error(t);try{return s=!0,e()}finally{s=!1}}}("Cannot dispatch recursively");constructor(s,e,{scheduler:i=new t(g,2e4),deliveryStrategy:n=a}={}){super(),this.O=s,this.st=new u(s,n),this.k=new h(e,i),this.k.addEventListener("message",this.nt),this.k.addEventListener("connected",this.ht),this.k.addEventListener("connectionfailure",this.ot),this.k.addEventListener("disconnected",this.rt)}dispatch=function(t){return Object.assign(t,{sync:(s=[])=>new Promise(((e,i)=>t(s,e,(t=>i(new Error(t))))))})}(((t,s,e)=>{if(!t.length&&!s&&!e)return;const i={lt:t,q:s,H:e};switch(this.S.Z){case-1:throw new Error("closed");case 0:this.S.tt.push(i);break;case 1:this.ct(this.ut(this.S.dt,[i])),this.gt._t()}}));ut(t,s){return this.it((()=>{for(const{lt:i,q:n,H:h}of s){if(i.length){const{S:s,$:n}=e(this.O,t,i);t=s,this.st.N(n)}this.st.U(t,n,h)}return t}))}gt=function(t){let s=null;const e=()=>{null!==s&&(clearTimeout(s),s=null)},i=()=>{e(),t()};return{ft:i,_t:()=>{null===s&&(s=setTimeout(i,0))},o:e}}((()=>{this.k.isConnected()&&!this.Y&&this.st.X(this.k.send)}));wt(t){if(-1!==this.S.Z){if(this.Y=!1,0===this.S.Z){const s=this.ut(t.init,this.S.tt);this.S={Z:1,vt:t.init,dt:s},this.ct(s,!0)}else this.S.vt=t.init,this.st.W(t.init),this.ct(this.st.V(t.init));this.gt.ft()}else this.bt(`Ignoring init after closing: ${JSON.stringify(t)}`)}yt(t){if(1!==this.S.Z)return void this.bt(`Ignoring change before init: ${JSON.stringify(t)}`);const s=this.S.vt=this.O.update(this.S.vt,t.change),{K:e,R:i}=this.st.B(t.id);i||this.ct(this.st.V(s)),e?.q.forEach((t=>t(s)))}Tt(t){if(1!==this.S.Z)return void this.bt(`Ignoring error before init: ${JSON.stringify(t)}`);const{K:s}=this.st.B(t.id);s?(this.bt(`API rejected update: ${t.error}`),s?.H.forEach((s=>s(t.error))),this.ct(this.st.V(this.S.vt))):this.bt(`API sent error: ${t.error}`)}xt(){this.k.send("x"),this.Y?this.bt("Unexpected extra close message"):(this.Y=!0,this.dispatchEvent(n("disconnected",_)))}nt=t=>{if("X"===t.detail)return void this.xt();const s=JSON.parse(t.detail);"change"in s?this.yt(s):"init"in s?this.wt(s):"error"in s?this.Tt(s):this.bt(`Ignoring unknown API message: ${t.detail}`)};addStateListener(t){this.et.add(t),1===this.S.Z&&t(this.S.dt)}removeStateListener(t){this.et.delete(t)}ct(t,s=!1){if(1!==this.S.Z)throw new Error("invalid state");if(s||this.S.dt!==t){this.S.dt=t;for(const s of this.et)s(t)}}getState(){return 1===this.S.Z?this.S.dt:void 0}bt(t){this.dispatchEvent(n("warning",new Error(t)))}ht=()=>{this.dispatchEvent(n("connected"))};ot=t=>{this.dispatchEvent(n("warning",t.detail))};rt=t=>{this.Y||(this.Y=!0,this.dispatchEvent(n("disconnected",t.detail)))};close(){this.Y=!0,this.S={Z:-1},this.k.close(),this.gt.o(),this.et.clear(),this.k.removeEventListener("message",this.nt),this.k.removeEventListener("connected",this.ht),this.k.removeEventListener("connectionfailure",this.ot),this.k.removeEventListener("disconnected",this.rt)}},exports.exponentialDelay=s;
@@ -1 +1 @@
1
- class t{t;i;h=null;o=null;l=null;u=0;constructor(t,s){this.t=t,this.i=s,this._=this._.bind(this)}trigger(t){this.stop(),this.l=t,this._()}schedule(t){this.l!==t&&(this.o&&this.stop(),this.l=t,null===this.h&&("hidden"===global.document?.visibilityState?global.addEventListener?.("visibilitychange",this._):(global.addEventListener?.("online",this._),this.h=setTimeout(this._,this.t(this.u))),global.addEventListener?.("pageshow",this._),global.addEventListener?.("focus",this._),++this.u))}stop(){this.l=null,this.o?.(),this.o=null,this.m()}async _(){if(this.o||!this.l)return;this.m();const t=new AbortController;let s=()=>null;this.o=()=>{s(),t.abort()};try{await Promise.race([this.l(t.signal),new Promise(((t,e)=>{const i=setTimeout(e,this.i);s=()=>clearTimeout(i)}))]),this.l=null,this.u=0}catch(s){if(!t.signal.aborted){t.abort();const s=this.l;s&&(this.l=null,this.schedule(s))}}finally{s(),this.o=null}}m(){null!==this.h&&(clearTimeout(this.h),this.h=null,global.removeEventListener?.("online",this._),global.removeEventListener?.("pageshow",this._),global.removeEventListener?.("visibilitychange",this._),global.removeEventListener?.("focus",this._))}}const s=({base:t=2,initialDelay:s,maxDelay:e,randomness:i=0})=>n=>Math.min(Math.pow(t,n)*s,e)*(1-Math.random()*i);function e(t,s,e){const i=[],n=[];function h(){if(n.length>0){const e=t.combine(n);i.push(e),s=t.update(s,e),n.length=0}}return function(t,s){let e={p:t,v:0,C:null};for(;e;)if(e.v>=e.p.length)e=e.C;else{const t=s(e.p[e.v]);++e.v,t&&t.length&&(e={p:t,v:0,C:e})}}(e,(t=>{if("function"==typeof t){h();return t(s)}return t&&n.push(t),null})),h(),{S:s,T:t.combine(i)}}class i extends EventTarget{addEventListener(t,s,e){super.addEventListener(t,s,e)}removeEventListener(t,s,e){super.removeEventListener(t,s,e)}dispatchEvent(t){return super.dispatchEvent(t)}}const n=(t,s)=>new CustomEvent(t,{detail:s});class h extends i{$;k;I=null;P=!1;constructor(t,s){super(),this.$=t,this.k=s,this.L=this.L.bind(this),this.k.trigger(this.L)}async L(t){const{url:s,token:e}=await this.$(t);t.throwIfAborted();const i=new AbortController,h=i.signal;await new Promise(((u,d)=>{const _=new WebSocket(s);let g=!0;const f=t=>{i.abort(),_.close(),g?(g=!1,this.dispatchEvent(n("connectionfailure",t)),d(new Error(`handshake failed: ${t.code} ${t.reason}`))):(this.I=null,this.dispatchEvent(n("disconnected",t)),this.P||this.k.schedule(this.L))};e&&_.addEventListener("open",(()=>_.send(e)),{once:!0,signal:h}),_.addEventListener("message",(t=>{t.data!==c&&(g&&(g=!1,this.I=_,this.dispatchEvent(n("connected")),u()),this.dispatchEvent(n("message",t.data)))}),{signal:h}),_.addEventListener("close",f,{signal:h}),_.addEventListener("error",(()=>f(o)),{signal:h}),t.addEventListener("abort",(()=>f(r)),{signal:h}),function(t){const s=new AbortController;let e=null;const i=()=>{null!==e&&(clearTimeout(e),e=null),t.send(l)},n=()=>{null!==e&&clearTimeout(e),e=setTimeout(i,a)},h=()=>{null!==e&&(clearTimeout(e),e=null),s.abort()};t.addEventListener("open",n,{once:!0,signal:s.signal}),t.addEventListener("message",n,{signal:s.signal}),t.addEventListener("close",h,{signal:s.signal}),t.addEventListener("error",h,{signal:s.signal}),global.addEventListener?.("offline",i,{signal:s.signal})}(_)})).catch((t=>{throw i.abort(),t}))}isConnected(){return null!==this.I}send=t=>{if(!this.I)throw new Error("connection lost");this.I.send(t)};close(){this.P=!0,this.k.stop(),this.I?.close()}}const o={code:0,reason:"client side error"},r={code:0,reason:"handshake timeout"},l="P",c="p",a=2e4,u=()=>!0,d=(t,s,e)=>!e;class _{M;A;O=[];j=function(){let t=1;return()=>t++}();constructor(t,s){this.M=t,this.A=s}D(t){this.O.push({J:void 0,N:t,F:[],G:[]})}q(t,s,e){if(s||e)if(this.O.length){const t=this.O[this.O.length-1];t.F.push(s??g),t.G.push(e??g)}else s&&Promise.resolve(t).then(s)}U(t){let s=0;for(let e=0;e<this.O.length;++e){const i=this.O[e];this.A(t,i.N,void 0!==i.J)?(i.J=void 0,this.O[e-s]=i):(i.G.forEach((t=>t("message possibly lost"))),++s)}this.O.length-=s}W(t){for(let t=0,s=0;t<=this.O.length;++t){const e=this.O[t];if(!e||void 0!==e.J||e.F.length>0||e.G.length>0){const e=t-s;if(e>1){const i=this.O[t-1],n=this.O.splice(s,e,i);i.N=this.M.combine(n.map((t=>t.N))),t-=e-1}s=t+1}}for(const s of this.O)void 0===s.J&&(s.J=this.j(),t(JSON.stringify({change:s.N,id:s.J})))}X(t){const s=void 0===t?-1:this.O.findIndex((s=>s.J===t));return-1===s?{B:null,H:!1}:{B:this.O.splice(s,1)[0],H:0===s}}K(t){if(!this.O.length)return t;const s=this.M.combine(this.O.map((({N:t})=>t)));return this.M.update(t,s)}}const g=()=>null;class f extends i{M;I;R=!0;S={V:0,Y:[]};Z;tt=new Set;st=function(t){let s=!1;return e=>{if(s)throw new Error(t);try{return s=!0,e()}finally{s=!1}}}("Cannot dispatch recursively");constructor(s,e,{scheduler:i=new t(w,2e4),deliveryStrategy:n=u}={}){super(),this.M=s,this.Z=new _(s,n),this.I=new h(e,i),this.I.addEventListener("message",this.et),this.I.addEventListener("connected",this.it),this.I.addEventListener("connectionfailure",this.nt),this.I.addEventListener("disconnected",this.ht)}dispatch=function(t){return Object.assign(t,{sync:(s=[])=>new Promise(((e,i)=>t(s,e,(t=>i(new Error(t))))))})}(((t,s,e)=>{if(!t.length&&!s&&!e)return;const i={ot:t,F:s,G:e};switch(this.S.V){case-1:throw new Error("closed");case 0:this.S.Y.push(i);break;case 1:this.rt(this.lt(this.S.ct,[i])),this.dt.ut()}}));lt(t,s){return this.st((()=>{for(const{ot:i,F:n,G:h}of s){if(i.length){const{S:s,T:n}=e(this.M,t,i);t=s,this.Z.D(n)}this.Z.q(t,n,h)}return t}))}dt=function(t){let s=null;const e=()=>{null!==s&&(clearTimeout(s),s=null)},i=()=>{e(),t()};return{_t:i,ut:()=>{null===s&&(s=setTimeout(i,0))},o:e}}((()=>{this.I.isConnected()&&!this.R&&this.Z.W(this.I.send)}));gt(t){if(-1!==this.S.V){if(this.R=!1,0===this.S.V){const s=this.lt(t.init,this.S.Y);this.S={V:1,ft:t.init,ct:s},this.rt(s,!0)}else this.S.ft=t.init,this.Z.U(t.init),this.rt(this.Z.K(t.init));this.dt._t()}else this.wt(`Ignoring init after closing: ${JSON.stringify(t)}`)}vt(t){if(1!==this.S.V)return void this.wt(`Ignoring change before init: ${JSON.stringify(t)}`);const s=this.S.ft=this.M.update(this.S.ft,t.change),{B:e,H:i}=this.Z.X(t.id);i||this.rt(this.Z.K(s)),e?.F.forEach((t=>t(s)))}bt(t){if(1!==this.S.V)return void this.wt(`Ignoring error before init: ${JSON.stringify(t)}`);const{B:s}=this.Z.X(t.id);s?(this.wt(`API rejected update: ${t.error}`),s?.G.forEach((s=>s(t.error))),this.rt(this.Z.K(this.S.ft))):this.wt(`API sent error: ${t.error}`)}yt(){this.I.send("x"),this.R?this.wt("Unexpected extra close message"):(this.R=!0,this.dispatchEvent(n("disconnected",m)))}et=t=>{if("X"===t.detail)return void this.yt();const s=JSON.parse(t.detail);"change"in s?this.vt(s):"init"in s?this.gt(s):"error"in s?this.bt(s):this.wt(`Ignoring unknown API message: ${t.detail}`)};addStateListener(t){this.tt.add(t),1===this.S.V&&t(this.S.ct)}removeStateListener(t){this.tt.delete(t)}rt(t,s=!1){if(1!==this.S.V)throw new Error("invalid state");if(s||this.S.ct!==t){this.S.ct=t;for(const s of this.tt)s(t)}}getState(){return 1===this.S.V?this.S.ct:void 0}wt(t){this.dispatchEvent(n("warning",new Error(t)))}it=()=>{this.dispatchEvent(n("connected"))};nt=t=>{this.dispatchEvent(n("warning",new Error(`connection failure: ${t.detail.code} ${t.detail.reason}`)))};ht=t=>{this.R||(this.R=!0,this.dispatchEvent(n("disconnected",t.detail)))};close(){this.R=!0,this.S={V:-1},this.I.close(),this.dt.o(),this.tt.clear(),this.I.removeEventListener("message",this.et),this.I.removeEventListener("connected",this.it),this.I.removeEventListener("connectionfailure",this.nt),this.I.removeEventListener("disconnected",this.ht)}}const m={code:0,reason:"graceful shutdown"},w=s({base:2,initialDelay:1e3,maxDelay:6e5,randomness:.3});export{u as AT_LEAST_ONCE,d as AT_MOST_ONCE,t as OnlineScheduler,f as SharedReducer,s as exponentialDelay};
1
+ class t{t;i;h=null;o=null;l=null;u=()=>null;_=0;constructor(t,s){this.t=t,this.i=s,this.m=this.m.bind(this)}trigger(t,s){this.stop(),this.l=t,this.u=s,this.m()}schedule(t,s){this.l!==t&&(this.o&&this.stop(),this.l=t,this.u=s,null===this.h&&("hidden"===globalThis.document?.visibilityState?globalThis.addEventListener?.("visibilitychange",this.m):(globalThis.addEventListener?.("online",this.m),this.h=setTimeout(this.m,this.t(this._))),globalThis.addEventListener?.("pageshow",this.m),globalThis.addEventListener?.("focus",this.m),++this._))}stop(){this.l=null,this.o?.(),this.o=null,this.p()}async m(){if(this.o||!this.l)return;this.p();const t=new AbortController,s=(t=>{const s={stop:()=>{}};return s.promise=new Promise(((i,e)=>{const n=setTimeout((()=>e(new Error(`Timed out after ${t}ms`))),t);s.stop=()=>clearTimeout(n)})),s})(this.i);this.o=()=>{s.stop(),t.abort()};try{await Promise.race([this.l(t.signal),s.promise]),this.l=null,this._=0}catch(s){if(!t.signal.aborted){t.abort();try{this.u(s)}catch(t){console.error("Error handler failed",s,t)}const i=this.l;i&&(this.l=null,this.schedule(i,this.u))}}finally{s.stop(),this.o=null}}p(){null!==this.h&&(clearTimeout(this.h),this.h=null,globalThis.removeEventListener?.("online",this.m),globalThis.removeEventListener?.("pageshow",this.m),globalThis.removeEventListener?.("visibilitychange",this.m),globalThis.removeEventListener?.("focus",this.m))}}const s=({base:t=2,initialDelay:s,maxDelay:i,randomness:e=0})=>n=>Math.min(Math.pow(t,n)*s,i)*(1-Math.random()*e);function i(t,s,i){const e=[],n=[];function h(){if(n.length>0){const i=t.combine(n);e.push(i),s=t.update(s,i),n.length=0}}return function(t,s){let i={v:t,T:0,C:null};for(;i;)if(i.T>=i.v.length)i=i.C;else{const t=s(i.v[i.T]);++i.T,t&&t.length&&(i={v:t,T:0,C:i})}}(i,(t=>{if("function"==typeof t){h();return t(s)}return t&&n.push(t),null})),h(),{S:s,$:t.combine(e)}}class e extends EventTarget{addEventListener(t,s,i){super.addEventListener(t,s,i)}removeEventListener(t,s,i){super.removeEventListener(t,s,i)}dispatchEvent(t){return super.dispatchEvent(t)}}const n=(t,s)=>new CustomEvent(t,{detail:s});class h extends e{I;P;k=null;L=!1;constructor(t,s){super(),this.I=t,this.P=s,this.M=this.M.bind(this),this.A=this.A.bind(this),this.P.trigger(this.M,this.A)}A(t){const s=t instanceof Error?t:new Error(`unknown connection error ${t}`);this.dispatchEvent(n("connectionfailure",s))}async M(t){const{url:s,token:i}=await this.I(t);if(t.aborted)throw t.reason;const e=new AbortController,h=e.signal;await new Promise(((a,u)=>{const d=new WebSocket(s);let _=!0;const g=t=>{e.abort(),d.close(),_?(_=!1,u(new Error(`Connection closed ${t.code} ${t.reason}`))):(this.k=null,this.dispatchEvent(n("disconnected",t)),this.L||this.P.schedule(this.M,this.A))};i&&d.addEventListener("open",(()=>d.send(i)),{once:!0,signal:h}),d.addEventListener("message",(t=>{t.data!==l&&(_&&(_=!1,this.k=d,this.dispatchEvent(n("connected")),a()),this.dispatchEvent(n("message",t.data)))}),{signal:h}),d.addEventListener("close",g,{signal:h}),d.addEventListener("error",(()=>g(o)),{signal:h}),t.addEventListener("abort",(()=>()=>{e.abort(),d.close(),_=!1,u(t.reason)}),{signal:h}),function(t){const s=new AbortController;let i=null;const e=()=>{null!==i&&(clearTimeout(i),i=null),t.send(r)},n=()=>{null!==i&&clearTimeout(i),i=setTimeout(e,c)},h=()=>{null!==i&&(clearTimeout(i),i=null),s.abort()};t.addEventListener("open",n,{once:!0,signal:s.signal}),t.addEventListener("message",n,{signal:s.signal}),t.addEventListener("close",h,{signal:s.signal}),t.addEventListener("error",h,{signal:s.signal}),globalThis.addEventListener?.("offline",e,{signal:s.signal})}(d)})).catch((t=>{throw e.abort(),t}))}isConnected(){return null!==this.k}send=t=>{if(!this.k)throw new Error("connection lost");this.k.send(t)};close(){this.L=!0,this.P.stop(),this.k?.close()}}const o={code:0,reason:"client side error"},r="P",l="p",c=2e4,a=()=>!0,u=(t,s,i)=>!i;class d{O;j;D=[];J=function(){let t=1;return()=>t++}();constructor(t,s){this.O=t,this.j=s}N(t){this.D.push({F:void 0,G:t,q:[],H:[]})}U(t,s,i){if(s||i)if(this.D.length){const t=this.D[this.D.length-1];t.q.push(s??_),t.H.push(i??_)}else s&&Promise.resolve(t).then(s)}W(t){let s=0;for(let i=0;i<this.D.length;++i){const e=this.D[i];this.j(t,e.G,void 0!==e.F)?(e.F=void 0,this.D[i-s]=e):(e.H.forEach((t=>t("message possibly lost"))),++s)}this.D.length-=s}X(t){for(let t=0,s=0;t<=this.D.length;++t){const i=this.D[t];if(!i||void 0!==i.F||i.q.length>0||i.H.length>0){const i=t-s;if(i>1){const e=this.D[t-1],n=this.D.splice(s,i,e);e.G=this.O.combine(n.map((t=>t.G))),t-=i-1}s=t+1}}for(const s of this.D)void 0===s.F&&(s.F=this.J(),t(JSON.stringify({change:s.G,id:s.F})))}B(t){const s=void 0===t?-1:this.D.findIndex((s=>s.F===t));return-1===s?{K:null,R:!1}:{K:this.D.splice(s,1)[0],R:0===s}}V(t){if(!this.D.length)return t;const s=this.O.combine(this.D.map((({G:t})=>t)));return this.O.update(t,s)}}const _=()=>null;class g extends e{O;k;Y=!0;S={Z:0,tt:[]};st;it=new Set;et=function(t){let s=!1;return i=>{if(s)throw new Error(t);try{return s=!0,i()}finally{s=!1}}}("Cannot dispatch recursively");constructor(s,i,{scheduler:e=new t(w,2e4),deliveryStrategy:n=a}={}){super(),this.O=s,this.st=new d(s,n),this.k=new h(i,e),this.k.addEventListener("message",this.nt),this.k.addEventListener("connected",this.ht),this.k.addEventListener("connectionfailure",this.ot),this.k.addEventListener("disconnected",this.rt)}dispatch=function(t){return Object.assign(t,{sync:(s=[])=>new Promise(((i,e)=>t(s,i,(t=>e(new Error(t))))))})}(((t,s,i)=>{if(!t.length&&!s&&!i)return;const e={lt:t,q:s,H:i};switch(this.S.Z){case-1:throw new Error("closed");case 0:this.S.tt.push(e);break;case 1:this.ct(this.ut(this.S.dt,[e])),this.gt._t()}}));ut(t,s){return this.et((()=>{for(const{lt:e,q:n,H:h}of s){if(e.length){const{S:s,$:n}=i(this.O,t,e);t=s,this.st.N(n)}this.st.U(t,n,h)}return t}))}gt=function(t){let s=null;const i=()=>{null!==s&&(clearTimeout(s),s=null)},e=()=>{i(),t()};return{ft:e,_t:()=>{null===s&&(s=setTimeout(e,0))},o:i}}((()=>{this.k.isConnected()&&!this.Y&&this.st.X(this.k.send)}));wt(t){if(-1!==this.S.Z){if(this.Y=!1,0===this.S.Z){const s=this.ut(t.init,this.S.tt);this.S={Z:1,vt:t.init,dt:s},this.ct(s,!0)}else this.S.vt=t.init,this.st.W(t.init),this.ct(this.st.V(t.init));this.gt.ft()}else this.bt(`Ignoring init after closing: ${JSON.stringify(t)}`)}yt(t){if(1!==this.S.Z)return void this.bt(`Ignoring change before init: ${JSON.stringify(t)}`);const s=this.S.vt=this.O.update(this.S.vt,t.change),{K:i,R:e}=this.st.B(t.id);e||this.ct(this.st.V(s)),i?.q.forEach((t=>t(s)))}Tt(t){if(1!==this.S.Z)return void this.bt(`Ignoring error before init: ${JSON.stringify(t)}`);const{K:s}=this.st.B(t.id);s?(this.bt(`API rejected update: ${t.error}`),s?.H.forEach((s=>s(t.error))),this.ct(this.st.V(this.S.vt))):this.bt(`API sent error: ${t.error}`)}Et(){this.k.send("x"),this.Y?this.bt("Unexpected extra close message"):(this.Y=!0,this.dispatchEvent(n("disconnected",f)))}nt=t=>{if("X"===t.detail)return void this.Et();const s=JSON.parse(t.detail);"change"in s?this.yt(s):"init"in s?this.wt(s):"error"in s?this.Tt(s):this.bt(`Ignoring unknown API message: ${t.detail}`)};addStateListener(t){this.it.add(t),1===this.S.Z&&t(this.S.dt)}removeStateListener(t){this.it.delete(t)}ct(t,s=!1){if(1!==this.S.Z)throw new Error("invalid state");if(s||this.S.dt!==t){this.S.dt=t;for(const s of this.it)s(t)}}getState(){return 1===this.S.Z?this.S.dt:void 0}bt(t){this.dispatchEvent(n("warning",new Error(t)))}ht=()=>{this.dispatchEvent(n("connected"))};ot=t=>{this.dispatchEvent(n("warning",t.detail))};rt=t=>{this.Y||(this.Y=!0,this.dispatchEvent(n("disconnected",t.detail)))};close(){this.Y=!0,this.S={Z:-1},this.k.close(),this.gt.o(),this.it.clear(),this.k.removeEventListener("message",this.nt),this.k.removeEventListener("connected",this.ht),this.k.removeEventListener("connectionfailure",this.ot),this.k.removeEventListener("disconnected",this.rt)}}const f={code:0,reason:"graceful shutdown"},w=s({base:2,initialDelay:1e3,maxDelay:6e5,randomness:.3});export{a as AT_LEAST_ONCE,u as AT_MOST_ONCE,t as OnlineScheduler,g as SharedReducer,s as exponentialDelay};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shared-reducer",
3
- "version": "5.0.1",
3
+ "version": "5.0.3",
4
4
  "description": "shared state management",
5
5
  "author": "David Evans",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "scripts": {
34
34
  "format": "prettier --write .",
35
- "test": "lean-test --preprocess=tsc --parallel-suites && package/build.sh && package/run.sh && tsc && prettier --check .",
35
+ "test": "lean-test --preprocess=tsc --parallel-suites && lean-test --target=chrome --preprocess=tsc --import-map --parallel-suites frontend/scheduler frontend/helpers && package/build.sh && package/run.sh && tsc && prettier --check .",
36
36
  "dopublish": "package/build.sh && npm publish package.tgz"
37
37
  },
38
38
  "repository": {