shared-reducer 5.0.0 → 5.0.2
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.
- package/frontend/index.d.ts +2 -1
- package/frontend/index.js +1 -1
- package/frontend/index.mjs +1 -1
- package/package.json +2 -2
package/frontend/index.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ type TypedEvent<Type extends string, T extends Event> = T & {
|
|
|
56
56
|
|
|
57
57
|
interface ConnectionInfo {
|
|
58
58
|
url: string;
|
|
59
|
-
token?: string;
|
|
59
|
+
token?: string | undefined;
|
|
60
60
|
}
|
|
61
61
|
type ConnectionGetter = (signal: AbortSignal) => MaybePromise<ConnectionInfo>;
|
|
62
62
|
interface DisconnectDetail {
|
|
@@ -100,6 +100,7 @@ declare class SharedReducer<T, SpecT> extends TypedEventTarget<SharedReducerEven
|
|
|
100
100
|
getState(): Readonly<T> | undefined;
|
|
101
101
|
private _warn;
|
|
102
102
|
private readonly _handleConnected;
|
|
103
|
+
private readonly _handleConnectionFailure;
|
|
103
104
|
private readonly _handleDisconnected;
|
|
104
105
|
close(): void;
|
|
105
106
|
}
|
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"===
|
|
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"===globalThis.document?.visibilityState?globalThis.addEventListener?.("visibilitychange",this._):(globalThis.addEventListener?.("online",this._),this.h=setTimeout(this._,this.t(this.u))),globalThis.addEventListener?.("pageshow",this._),globalThis.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,globalThis.removeEventListener?.("online",this._),globalThis.removeEventListener?.("pageshow",this._),globalThis.removeEventListener?.("visibilitychange",this._),globalThis.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,T:null};for(;e;)if(e.v>=e.p.length)e=e.T;else{const t=s(e.p[e.v]);++e.v,t&&t.length&&(e={p:t,v:0,T:e})}}(e,(t=>{if("function"==typeof t){h();return t(s)}return t&&n.push(t),null})),h(),{C: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{$;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}),globalThis.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;C={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.C.V){case-1:throw new Error("closed");case 0:this.C.Y.push(i);break;case 1:this.rt(this.lt(this.C.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{C:s,S: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.C.V){if(this.R=!1,0===this.C.V){const s=this.lt(t.init,this.C.Y);this.C={V:1,ft:t.init,ct:s},this.rt(s,!0)}else this.C.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.C.V)return void this.wt(`Ignoring change before init: ${JSON.stringify(t)}`);const s=this.C.ft=this.M.update(this.C.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.C.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.C.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.C.V&&t(this.C.ct)}removeStateListener(t){this.tt.delete(t)}rt(t,s=!1){if(1!==this.C.V)throw new Error("invalid state");if(s||this.C.ct!==t){this.C.ct=t;for(const s of this.tt)s(t)}}getState(){return 1===this.C.V?this.C.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.C={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;
|
package/frontend/index.mjs
CHANGED
|
@@ -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"===
|
|
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"===globalThis.document?.visibilityState?globalThis.addEventListener?.("visibilitychange",this._):(globalThis.addEventListener?.("online",this._),this.h=setTimeout(this._,this.t(this.u))),globalThis.addEventListener?.("pageshow",this._),globalThis.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,globalThis.removeEventListener?.("online",this._),globalThis.removeEventListener?.("pageshow",this._),globalThis.removeEventListener?.("visibilitychange",this._),globalThis.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,T:null};for(;e;)if(e.v>=e.p.length)e=e.T;else{const t=s(e.p[e.v]);++e.v,t&&t.length&&(e={p:t,v:0,T:e})}}(e,(t=>{if("function"==typeof t){h();return t(s)}return t&&n.push(t),null})),h(),{C: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{$;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}),globalThis.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;C={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.C.V){case-1:throw new Error("closed");case 0:this.C.Y.push(i);break;case 1:this.rt(this.lt(this.C.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{C:s,S: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.C.V){if(this.R=!1,0===this.C.V){const s=this.lt(t.init,this.C.Y);this.C={V:1,ft:t.init,ct:s},this.rt(s,!0)}else this.C.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.C.V)return void this.wt(`Ignoring change before init: ${JSON.stringify(t)}`);const s=this.C.ft=this.M.update(this.C.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.C.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.C.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.C.V&&t(this.C.ct)}removeStateListener(t){this.tt.delete(t)}rt(t,s=!1){if(1!==this.C.V)throw new Error("invalid state");if(s||this.C.ct!==t){this.C.ct=t;for(const s of this.tt)s(t)}}getState(){return 1===this.C.V?this.C.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.C={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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shared-reducer",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
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": {
|