shared-reducer 5.0.2 → 5.1.0

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.
@@ -103,6 +103,11 @@ interface WebsocketHandlerOptions {
103
103
  pingInterval?: number;
104
104
  pongTimeout?: number;
105
105
  }
106
+ interface HandlerCallbacks<Req> {
107
+ onConnect?: (req: Req) => void;
108
+ onDisconnect?: (req: Req, reason: string, connectionDuration: number) => void;
109
+ onError?: (req: Req, context: string, error: unknown) => void;
110
+ }
106
111
  declare class WebsocketHandlerFactory<T, SpecT> {
107
112
  private readonly broadcaster;
108
113
  private readonly closers;
@@ -112,7 +117,7 @@ declare class WebsocketHandlerFactory<T, SpecT> {
112
117
  constructor(broadcaster: Broadcaster<T, SpecT>, options?: WebsocketHandlerOptions);
113
118
  activeConnections(): number;
114
119
  softClose(timeout: number): Promise<void>;
115
- handler<Req, Res extends WSResponse>(idGetter: (req: Req, res: Res) => MaybePromise<string>, permissionGetter: (req: Req, res: Res) => MaybePromise<Permission<T, SpecT>>): (req: Req, res: Res) => Promise<void>;
120
+ handler<Req, Res extends WSResponse>(idGetter: (req: Req, res: Res) => MaybePromise<string>, permissionGetter: (req: Req, res: Res) => MaybePromise<Permission<T, SpecT>>, { onConnect, onDisconnect, onError }?: HandlerCallbacks<Req>): (req: Req, res: Res) => Promise<void>;
116
121
  }
117
122
 
118
123
  declare const UniqueIdProvider: () => () => string;
package/backend/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var t=require("node:crypto");const e=()=>{const e=t.randomUUID().substring(0,8);let s=0;return()=>{const t=s++;return`${e}-${t}`}};class s extends EventTarget{t=[];i=!1;push(t){return new Promise(((e,s)=>{this.t.push((async()=>{try{e(await t())}catch(t){s(t)}})),this.i||this.o()}))}async o(){for(this.i=!0;this.t.length>0;)await this.t.shift()();this.i=!1,this.dispatchEvent(new CustomEvent("drain"))}active(){return this.i}}class r{h;l=new Map;constructor(t=()=>new s){this.h=t}push(t,e){let s=this.l.get(t);if(!s){const e=this.h(),r=()=>{e.active()||(this.l.delete(t),e.removeEventListener("drain",r))};e.addEventListener("drain",r),this.l.set(t,e),s=e}return s.push(e)}}class i{u;p=new Map;constructor(t){this.u=t}async add(t,e){let s=this.p.get(t);s||(s=this.u(t),this.p.set(t,s)),await s.add(e)}async remove(t,e){const s=this.p.get(t);if(s){await s.remove(e)||this.p.delete(t)}}async broadcast(t,e){const s=this.p.get(t);s&&await s.broadcast(e)}}class n{m=new Set;add(t){this.m.add(t)}remove(t){return this.m.delete(t),this.m.size>0}broadcast(t){this.m.forEach((e=>e(t)))}}const o={validateWrite(){}};const a=t=>t;class c extends Error{}const h="Cannot modify data",l={validateWriteSpec(){throw new c(h)},validateWrite(){throw new c(h)}};exports.AsyncTaskQueue=s,exports.Broadcaster=class{v;_;m;S;C;constructor(t,s,o={}){this.v=t,this._=s,this.m=o.subscribers??new i((()=>new n)),this.S=o.taskQueues??new r,this.C=o.idProvider??e()}async subscribe(t,e=o){let s={T:0},r="";const i=t=>{2===s.T?t.source===r?s.O(t.message,t.meta):t.message.change&&s.O(t.message,void 0):1===s.T&&s.t.push(t)};try{if(await this.S.push(t,(async()=>{const e=await this.v.read(t);null!=e&&(s={T:1,P:e,t:[]},await this.m.add(t,i))})),0===s.T)return null;r=await this.C()}catch(e){throw await this.m.remove(t,i),e}return{getInitialData(){if(1!==s.T)throw new Error("Already started");return s.P},listen(t){if(1!==s.T)throw new Error("Already started");const e=s.t;s={T:2,O:t},e.forEach(i)},send:(s,i)=>this.j(t,s,e,r,i),close:async()=>{await this.m.remove(t,i)}}}update(t,e,s=o){return this.j(t,e,s,null,void 0)}async I(t,e,s,r,i){try{const r=await this.v.read(t);if(!r)throw new Error("Deleted");s.validateWriteSpec?.(e);const i=this._.update(r,e),n=this.v.validate(i);s.validateWrite(n,r),await this.v.write(t,n,r)}catch(e){return void this.m.broadcast(t,{message:{error:e instanceof Error?e.message:"Internal error"},source:r,meta:i})}this.m.broadcast(t,{message:{change:e},source:r,meta:i})}async j(t,e,s,r,i){return this.S.push(t,(()=>this.I(t,e,s,r,i)))}},exports.CLOSE="X",exports.CLOSE_ACK="x",exports.CollectionStorageModel=class{W;k;validate;q;$;constructor(t,e,s,r={}){this.W=t,this.k=e,this.validate=s,this.q=r.readErrorIntercept??a,this.$=r.writeErrorIntercept??a}async read(t){try{return await this.W.get(this.k,t)}catch(t){throw this.q(t)}}async write(t,e,s){const r={};Object.entries(e).forEach((([t,e])=>{const i=t;e!==(Object.prototype.hasOwnProperty.call(s,i)?s[i]:void 0)&&(r[i]?Object.defineProperty(r,i,{value:e,configurable:!0,enumerable:!0,writable:!0}):r[i]=e)}));try{await this.W.update(this.k,t,r)}catch(t){throw this.$(t)}}},exports.InMemoryModel=class{read=this.get;validate;A=new Map;constructor(t=t=>t){this.validate=t}set(t,e){this.A.set(t,e)}get(t){return this.A.get(t)}delete(t){this.A.delete(t)}write(t,e,s){if(s!==this.A.get(t))throw new Error("Unexpected previous value");this.A.set(t,e)}},exports.InMemoryTopic=n,exports.PING="P",exports.PONG="p",exports.PermissionError=c,exports.ReadOnly=l,exports.ReadWrite=o,exports.ReadWriteStruct=class{J;constructor(t=[]){this.J=t}validateWrite(t,e){for(const s of this.J){const r=Object.prototype.hasOwnProperty.call(e,s),i=Object.prototype.hasOwnProperty.call(t,s);if(r!==i)throw new c(r?`Cannot remove field ${String(s)}`:`Cannot add field ${String(s)}`);if(i&&e[s]!==t[s])throw new c(`Cannot edit field ${String(s)}`)}}},exports.TaskQueueMap=r,exports.TrackingTopicMap=i,exports.UniqueIdProvider=e,exports.WebsocketHandlerFactory=class{broadcaster;closers=new Set;M;N;closing=!1;constructor(t,e={}){this.broadcaster=t,this.M=e.pingInterval??25e3,this.N=e.pongTimeout??3e4}activeConnections(){return this.closers.size}async softClose(t){this.closing=!0;let e=null;await Promise.race([Promise.all([...this.closers].map((t=>t()))),new Promise((s=>{e=setTimeout(s,t)}))]),null!==e&&clearTimeout(e)}handler(t,e){const s=async(s,r)=>{const i=await t(s,r),n=await e(s,r),o=await this.broadcaster.subscribe(i,n);return o||(r.sendError(404),null)};return async(t,e)=>{if(this.closing)return void e.sendError(503,1012);const r=await s(t,e).catch((t=>(console.warn("WebSocket init error",t),e.sendError(500),null)));if(r)try{const t=await e.accept();let s=0,i=()=>null;const n=()=>(this.closers.delete(n),t.send("X"),s=1,new Promise((t=>{i=t})));t.on("close",(()=>{s=2,clearTimeout(c),r.close().catch((()=>null)),this.closers.delete(n),i()}));const o=()=>{r.close().catch((()=>null)),this.closers.delete(n),t.terminate(),i()},a=()=>{t.ping(),clearTimeout(c),c=setTimeout(o,this.N)};if(t.on("pong",(()=>{clearTimeout(c),c=setTimeout(a,this.M)})),t.on("message",(async(n,o)=>{clearTimeout(c),c=setTimeout(a,this.M);try{if(o)throw new Error("Binary messages are not supported");const a=String(n);if("P"===a)return void t.send("p");if("x"===a){if(1!==s)throw new Error("Unexpected close ack message");return s=2,void i()}if(2===s)throw new Error("Unexpected message after close ack");const c=function(t){const e=JSON.parse(t);if("object"!=typeof e||!e||Array.isArray(e))throw new Error("Must specify change and optional id");const{id:s,change:r}=e;if(void 0===s)return{change:r};if("number"!=typeof s)throw new Error("if specified, id must be a number");return{change:r,id:s}}(a);e.beginTransaction();try{await r.send(c.change,c.id)}finally{e.endTransaction()}}catch(e){t.send(JSON.stringify({error:e instanceof Error?e.message:"Internal error"}))}})),this.closing)return e.sendError(503,1012),void r.close().catch((()=>null));t.send(JSON.stringify({init:r.getInitialData()})),r.listen(((e,s)=>{const r=void 0!==s?{id:s,...e}:e;t.send(JSON.stringify(r))}));let c=setTimeout(a,this.M);this.closers.add(n)}catch(t){console.warn("WebSocket error",t),e.sendError(500),r.close().catch((()=>null))}}}};
1
+ "use strict";var t=require("node:crypto");const e=()=>{const e=t.randomUUID().substring(0,8);let s=0;return()=>{const t=s++;return`${e}-${t}`}};class s extends EventTarget{t=[];i=!1;push(t){return new Promise(((e,s)=>{this.t.push((async()=>{try{e(await t())}catch(t){s(t)}})),this.i||this.o()}))}async o(){for(this.i=!0;this.t.length>0;)await this.t.shift()();this.i=!1,this.dispatchEvent(new CustomEvent("drain"))}active(){return this.i}}class r{h;u=new Map;constructor(t=()=>new s){this.h=t}push(t,e){let s=this.u.get(t);if(!s){const e=this.h(),r=()=>{e.active()||(this.u.delete(t),e.removeEventListener("drain",r))};e.addEventListener("drain",r),this.u.set(t,e),s=e}return s.push(e)}}class i{l;p=new Map;constructor(t){this.l=t}async add(t,e){let s=this.p.get(t);s||(s=this.l(t),this.p.set(t,s)),await s.add(e)}async remove(t,e){const s=this.p.get(t);if(s){await s.remove(e)||this.p.delete(t)}}async broadcast(t,e){const s=this.p.get(t);s&&await s.broadcast(e)}}class n{m=new Set;add(t){this.m.add(t)}remove(t){return this.m.delete(t),this.m.size>0}broadcast(t){this.m.forEach((e=>e(t)))}}const o={validateWrite(){}};const a=(t,e,s)=>console.warn(`shared-reducer: ${e}`,s),c=t=>t;class h extends Error{}const u="Cannot modify data",l={validateWriteSpec(){throw new h(u)},validateWrite(){throw new h(u)}};exports.AsyncTaskQueue=s,exports.Broadcaster=class{v;_;m;C;T;constructor(t,s,o={}){this.v=t,this._=s,this.m=o.subscribers??new i((()=>new n)),this.C=o.taskQueues??new r,this.T=o.idProvider??e()}async subscribe(t,e=o){let s={S:0},r="";const i=t=>{2===s.S?t.source===r?s.O(t.message,t.meta):t.message.change&&s.O(t.message,void 0):1===s.S&&s.t.push(t)};try{if(await this.C.push(t,(async()=>{const e=await this.v.read(t);null!=e&&(s={S:1,P:e,t:[]},await this.m.add(t,i))})),0===s.S)return null;r=await this.T()}catch(e){throw await this.m.remove(t,i),e}return{getInitialData(){if(1!==s.S)throw new Error("Already started");return s.P},listen(t){if(1!==s.S)throw new Error("Already started");const e=s.t;s={S:2,O:t},e.forEach(i)},send:(s,i)=>this.j(t,s,e,r,i),close:async()=>{await this.m.remove(t,i)}}}update(t,e,s=o){return this.j(t,e,s,null,void 0)}async D(t,e,s,r,i){try{const r=await this.v.read(t);if(!r)throw new Error("Deleted");s.validateWriteSpec?.(e);const i=this._.update(r,e),n=this.v.validate(i);s.validateWrite(n,r),await this.v.write(t,n,r)}catch(e){return void this.m.broadcast(t,{message:{error:e instanceof Error?e.message:"Internal error"},source:r,meta:i})}this.m.broadcast(t,{message:{change:e},source:r,meta:i})}async j(t,e,s,r,i){return this.C.push(t,(()=>this.D(t,e,s,r,i)))}},exports.CLOSE="X",exports.CLOSE_ACK="x",exports.CollectionStorageModel=class{I;$;validate;k;q;constructor(t,e,s,r={}){this.I=t,this.$=e,this.validate=s,this.k=r.readErrorIntercept??c,this.q=r.writeErrorIntercept??c}async read(t){try{return await this.I.get(this.$,t)}catch(t){throw this.k(t)}}async write(t,e,s){const r={};Object.entries(e).forEach((([t,e])=>{const i=t;e!==(Object.prototype.hasOwnProperty.call(s,i)?s[i]:void 0)&&(r[i]?Object.defineProperty(r,i,{value:e,configurable:!0,enumerable:!0,writable:!0}):r[i]=e)}));try{await this.I.update(this.$,t,r)}catch(t){throw this.q(t)}}},exports.InMemoryModel=class{read=this.get;validate;A=new Map;constructor(t=t=>t){this.validate=t}set(t,e){this.A.set(t,e)}get(t){return this.A.get(t)}delete(t){this.A.delete(t)}write(t,e,s){if(s!==this.A.get(t))throw new Error("Unexpected previous value");this.A.set(t,e)}},exports.InMemoryTopic=n,exports.PING="P",exports.PONG="p",exports.PermissionError=h,exports.ReadOnly=l,exports.ReadWrite=o,exports.ReadWriteStruct=class{J;constructor(t=[]){this.J=t}validateWrite(t,e){for(const s of this.J){const r=Object.prototype.hasOwnProperty.call(e,s),i=Object.prototype.hasOwnProperty.call(t,s);if(r!==i)throw new h(r?`Cannot remove field ${String(s)}`:`Cannot add field ${String(s)}`);if(i&&e[s]!==t[s])throw new h(`Cannot edit field ${String(s)}`)}}},exports.TaskQueueMap=r,exports.TrackingTopicMap=i,exports.UniqueIdProvider=e,exports.WebsocketHandlerFactory=class{broadcaster;closers=new Set;M;N;closing=!1;constructor(t,e={}){this.broadcaster=t,this.M=e.pingInterval??25e3,this.N=e.pongTimeout??3e4}activeConnections(){return this.closers.size}async softClose(t){this.closing=!0;let e=null;await Promise.race([Promise.all([...this.closers].map((t=>t()))),new Promise((s=>{e=setTimeout(s,t)}))]),null!==e&&clearTimeout(e)}handler(t,e,{onConnect:s,onDisconnect:r,onError:i=a}={}){const n=async(s,r)=>{const i=await t(s,r),n=await e(s,r),o=await this.broadcaster.subscribe(i,n);return o||(r.sendError(404),null)};return async(t,e)=>{if(this.closing)return void e.sendError(503,1012);const o=(e,s)=>{try{i(t,e,s)}catch{}},a=await n(t,e).catch((t=>(o("handshake",t),e.sendError(500),null)));if(!a)return;const c=Date.now(),h=e=>{const s=Date.now()-c;try{r?.(t,e,s)}catch(t){o("disconnect hook",t)}a.close().catch((()=>null))};try{const r=await e.accept();s?.(t);let i=0,n=()=>null;const o=()=>(this.closers.delete(o),r.send("X"),i=1,new Promise((t=>{n=t})));r.on("close",(()=>{i=2,clearTimeout(l),h("disconnect"),this.closers.delete(o),n()}));const c=()=>{h("lost"),this.closers.delete(o),r.terminate(),n()},u=()=>{r.ping(),clearTimeout(l),l=setTimeout(c,this.N)};if(r.on("pong",(()=>{clearTimeout(l),l=setTimeout(u,this.M)})),r.on("message",(async(t,s)=>{clearTimeout(l),l=setTimeout(u,this.M);try{if(s)throw new Error("Binary messages are not supported");const o=String(t);if("P"===o)return void r.send("p");if("x"===o){if(1!==i)throw new Error("Unexpected close ack message");return i=2,void n()}if(2===i)throw new Error("Unexpected message after close ack");const c=function(t){const e=JSON.parse(t);if("object"!=typeof e||!e||Array.isArray(e))throw new Error("Must specify change and optional id");const{id:s,change:r}=e;if(void 0===s)return{change:r};if("number"!=typeof s)throw new Error("if specified, id must be a number");return{change:r,id:s}}(o);e.beginTransaction();try{await a.send(c.change,c.id)}finally{e.endTransaction()}}catch(t){r.send(JSON.stringify({error:t instanceof Error?t.message:"Internal error"}))}})),this.closing)return e.sendError(503,1012),void h("server shutdown");r.send(JSON.stringify({init:a.getInitialData()})),a.listen(((t,e)=>{const s=void 0!==e?{id:e,...t}:t;r.send(JSON.stringify(s))}));let l=setTimeout(u,this.M);this.closers.add(o)}catch(t){o("communication",t),e.sendError(500),h("error")}}}};
package/backend/index.mjs CHANGED
@@ -1 +1 @@
1
- import{randomUUID as t}from"node:crypto";const e=()=>{const e=t().substring(0,8);let s=0;return()=>{const t=s++;return`${e}-${t}`}};class s extends EventTarget{t=[];i=!1;push(t){return new Promise(((e,s)=>{this.t.push((async()=>{try{e(await t())}catch(t){s(t)}})),this.i||this.o()}))}async o(){for(this.i=!0;this.t.length>0;)await this.t.shift()();this.i=!1,this.dispatchEvent(new CustomEvent("drain"))}active(){return this.i}}class r{h;l=new Map;constructor(t=()=>new s){this.h=t}push(t,e){let s=this.l.get(t);if(!s){const e=this.h(),r=()=>{e.active()||(this.l.delete(t),e.removeEventListener("drain",r))};e.addEventListener("drain",r),this.l.set(t,e),s=e}return s.push(e)}}class i{u;m=new Map;constructor(t){this.u=t}async add(t,e){let s=this.m.get(t);s||(s=this.u(t),this.m.set(t,s)),await s.add(e)}async remove(t,e){const s=this.m.get(t);if(s){await s.remove(e)||this.m.delete(t)}}async broadcast(t,e){const s=this.m.get(t);s&&await s.broadcast(e)}}class n{p=new Set;add(t){this.p.add(t)}remove(t){return this.p.delete(t),this.p.size>0}broadcast(t){this.p.forEach((e=>e(t)))}}const a={validateWrite(){}};class o{_;v;p;S;C;constructor(t,s,a={}){this._=t,this.v=s,this.p=a.subscribers??new i((()=>new n)),this.S=a.taskQueues??new r,this.C=a.idProvider??e()}async subscribe(t,e=a){let s={T:0},r="";const i=t=>{2===s.T?t.source===r?s.O(t.message,t.meta):t.message.change&&s.O(t.message,void 0):1===s.T&&s.t.push(t)};try{if(await this.S.push(t,(async()=>{const e=await this._.read(t);null!=e&&(s={T:1,P:e,t:[]},await this.p.add(t,i))})),0===s.T)return null;r=await this.C()}catch(e){throw await this.p.remove(t,i),e}return{getInitialData(){if(1!==s.T)throw new Error("Already started");return s.P},listen(t){if(1!==s.T)throw new Error("Already started");const e=s.t;s={T:2,O:t},e.forEach(i)},send:(s,i)=>this.j(t,s,e,r,i),close:async()=>{await this.p.remove(t,i)}}}update(t,e,s=a){return this.j(t,e,s,null,void 0)}async I(t,e,s,r,i){try{const r=await this._.read(t);if(!r)throw new Error("Deleted");s.validateWriteSpec?.(e);const i=this.v.update(r,e),n=this._.validate(i);s.validateWrite(n,r),await this._.write(t,n,r)}catch(e){return void this.p.broadcast(t,{message:{error:e instanceof Error?e.message:"Internal error"},source:r,meta:i})}this.p.broadcast(t,{message:{change:e},source:r,meta:i})}async j(t,e,s,r,i){return this.S.push(t,(()=>this.I(t,e,s,r,i)))}}const c="P",h="p",l="X",u="x";class w{broadcaster;closers=new Set;W;k;closing=!1;constructor(t,e={}){this.broadcaster=t,this.W=e.pingInterval??25e3,this.k=e.pongTimeout??3e4}activeConnections(){return this.closers.size}async softClose(t){this.closing=!0;let e=null;await Promise.race([Promise.all([...this.closers].map((t=>t()))),new Promise((s=>{e=setTimeout(s,t)}))]),null!==e&&clearTimeout(e)}handler(t,e){const s=async(s,r)=>{const i=await t(s,r),n=await e(s,r),a=await this.broadcaster.subscribe(i,n);return a||(r.sendError(404),null)};return async(t,e)=>{if(this.closing)return void e.sendError(503,1012);const r=await s(t,e).catch((t=>(console.warn("WebSocket init error",t),e.sendError(500),null)));if(r)try{const t=await e.accept();let s=0,i=()=>null;const n=()=>(this.closers.delete(n),t.send("X"),s=1,new Promise((t=>{i=t})));t.on("close",(()=>{s=2,clearTimeout(c),r.close().catch((()=>null)),this.closers.delete(n),i()}));const a=()=>{r.close().catch((()=>null)),this.closers.delete(n),t.terminate(),i()},o=()=>{t.ping(),clearTimeout(c),c=setTimeout(a,this.k)};if(t.on("pong",(()=>{clearTimeout(c),c=setTimeout(o,this.W)})),t.on("message",(async(n,a)=>{clearTimeout(c),c=setTimeout(o,this.W);try{if(a)throw new Error("Binary messages are not supported");const o=String(n);if("P"===o)return void t.send("p");if("x"===o){if(1!==s)throw new Error("Unexpected close ack message");return s=2,void i()}if(2===s)throw new Error("Unexpected message after close ack");const c=function(t){const e=JSON.parse(t);if("object"!=typeof e||!e||Array.isArray(e))throw new Error("Must specify change and optional id");const{id:s,change:r}=e;if(void 0===s)return{change:r};if("number"!=typeof s)throw new Error("if specified, id must be a number");return{change:r,id:s}}(o);e.beginTransaction();try{await r.send(c.change,c.id)}finally{e.endTransaction()}}catch(e){t.send(JSON.stringify({error:e instanceof Error?e.message:"Internal error"}))}})),this.closing)return e.sendError(503,1012),void r.close().catch((()=>null));t.send(JSON.stringify({init:r.getInitialData()})),r.listen(((e,s)=>{const r=void 0!==s?{id:s,...e}:e;t.send(JSON.stringify(r))}));let c=setTimeout(o,this.W);this.closers.add(n)}catch(t){console.warn("WebSocket error",t),e.sendError(500),r.close().catch((()=>null))}}}}const d=t=>t;class f{$;q;validate;A;J;constructor(t,e,s,r={}){this.$=t,this.q=e,this.validate=s,this.A=r.readErrorIntercept??d,this.J=r.writeErrorIntercept??d}async read(t){try{return await this.$.get(this.q,t)}catch(t){throw this.A(t)}}async write(t,e,s){const r={};Object.entries(e).forEach((([t,e])=>{const i=t;e!==(Object.prototype.hasOwnProperty.call(s,i)?s[i]:void 0)&&(r[i]?Object.defineProperty(r,i,{value:e,configurable:!0,enumerable:!0,writable:!0}):r[i]=e)}));try{await this.$.update(this.q,t,r)}catch(t){throw this.J(t)}}}class m extends Error{}class y{read=this.get;validate;M=new Map;constructor(t=t=>t){this.validate=t}set(t,e){this.M.set(t,e)}get(t){return this.M.get(t)}delete(t){this.M.delete(t)}write(t,e,s){if(s!==this.M.get(t))throw new Error("Unexpected previous value");this.M.set(t,e)}}const p="Cannot modify data",g={validateWriteSpec(){throw new m(p)},validateWrite(){throw new m(p)}};class _{N;constructor(t=[]){this.N=t}validateWrite(t,e){for(const s of this.N){const r=Object.prototype.hasOwnProperty.call(e,s),i=Object.prototype.hasOwnProperty.call(t,s);if(r!==i)throw new m(r?`Cannot remove field ${String(s)}`:`Cannot add field ${String(s)}`);if(i&&e[s]!==t[s])throw new m(`Cannot edit field ${String(s)}`)}}}export{s as AsyncTaskQueue,o as Broadcaster,l as CLOSE,u as CLOSE_ACK,f as CollectionStorageModel,y as InMemoryModel,n as InMemoryTopic,c as PING,h as PONG,m as PermissionError,g as ReadOnly,a as ReadWrite,_ as ReadWriteStruct,r as TaskQueueMap,i as TrackingTopicMap,e as UniqueIdProvider,w as WebsocketHandlerFactory};
1
+ import{randomUUID as t}from"node:crypto";const e=()=>{const e=t().substring(0,8);let s=0;return()=>{const t=s++;return`${e}-${t}`}};class s extends EventTarget{t=[];i=!1;push(t){return new Promise(((e,s)=>{this.t.push((async()=>{try{e(await t())}catch(t){s(t)}})),this.i||this.o()}))}async o(){for(this.i=!0;this.t.length>0;)await this.t.shift()();this.i=!1,this.dispatchEvent(new CustomEvent("drain"))}active(){return this.i}}class r{h;u=new Map;constructor(t=()=>new s){this.h=t}push(t,e){let s=this.u.get(t);if(!s){const e=this.h(),r=()=>{e.active()||(this.u.delete(t),e.removeEventListener("drain",r))};e.addEventListener("drain",r),this.u.set(t,e),s=e}return s.push(e)}}class i{l;m=new Map;constructor(t){this.l=t}async add(t,e){let s=this.m.get(t);s||(s=this.l(t),this.m.set(t,s)),await s.add(e)}async remove(t,e){const s=this.m.get(t);if(s){await s.remove(e)||this.m.delete(t)}}async broadcast(t,e){const s=this.m.get(t);s&&await s.broadcast(e)}}class n{p=new Set;add(t){this.p.add(t)}remove(t){return this.p.delete(t),this.p.size>0}broadcast(t){this.p.forEach((e=>e(t)))}}const a={validateWrite(){}};class o{v;_;p;C;T;constructor(t,s,a={}){this.v=t,this._=s,this.p=a.subscribers??new i((()=>new n)),this.C=a.taskQueues??new r,this.T=a.idProvider??e()}async subscribe(t,e=a){let s={S:0},r="";const i=t=>{2===s.S?t.source===r?s.O(t.message,t.meta):t.message.change&&s.O(t.message,void 0):1===s.S&&s.t.push(t)};try{if(await this.C.push(t,(async()=>{const e=await this.v.read(t);null!=e&&(s={S:1,P:e,t:[]},await this.p.add(t,i))})),0===s.S)return null;r=await this.T()}catch(e){throw await this.p.remove(t,i),e}return{getInitialData(){if(1!==s.S)throw new Error("Already started");return s.P},listen(t){if(1!==s.S)throw new Error("Already started");const e=s.t;s={S:2,O:t},e.forEach(i)},send:(s,i)=>this.j(t,s,e,r,i),close:async()=>{await this.p.remove(t,i)}}}update(t,e,s=a){return this.j(t,e,s,null,void 0)}async D(t,e,s,r,i){try{const r=await this.v.read(t);if(!r)throw new Error("Deleted");s.validateWriteSpec?.(e);const i=this._.update(r,e),n=this.v.validate(i);s.validateWrite(n,r),await this.v.write(t,n,r)}catch(e){return void this.p.broadcast(t,{message:{error:e instanceof Error?e.message:"Internal error"},source:r,meta:i})}this.p.broadcast(t,{message:{change:e},source:r,meta:i})}async j(t,e,s,r,i){return this.C.push(t,(()=>this.D(t,e,s,r,i)))}}const c="P",h="p",u="X",l="x";class d{broadcaster;closers=new Set;I;$;closing=!1;constructor(t,e={}){this.broadcaster=t,this.I=e.pingInterval??25e3,this.$=e.pongTimeout??3e4}activeConnections(){return this.closers.size}async softClose(t){this.closing=!0;let e=null;await Promise.race([Promise.all([...this.closers].map((t=>t()))),new Promise((s=>{e=setTimeout(s,t)}))]),null!==e&&clearTimeout(e)}handler(t,e,{onConnect:s,onDisconnect:r,onError:i=w}={}){const n=async(s,r)=>{const i=await t(s,r),n=await e(s,r),a=await this.broadcaster.subscribe(i,n);return a||(r.sendError(404),null)};return async(t,e)=>{if(this.closing)return void e.sendError(503,1012);const a=(e,s)=>{try{i(t,e,s)}catch{}},o=await n(t,e).catch((t=>(a("handshake",t),e.sendError(500),null)));if(!o)return;const c=Date.now(),h=e=>{const s=Date.now()-c;try{r?.(t,e,s)}catch(t){a("disconnect hook",t)}o.close().catch((()=>null))};try{const r=await e.accept();s?.(t);let i=0,n=()=>null;const a=()=>(this.closers.delete(a),r.send("X"),i=1,new Promise((t=>{n=t})));r.on("close",(()=>{i=2,clearTimeout(l),h("disconnect"),this.closers.delete(a),n()}));const c=()=>{h("lost"),this.closers.delete(a),r.terminate(),n()},u=()=>{r.ping(),clearTimeout(l),l=setTimeout(c,this.$)};if(r.on("pong",(()=>{clearTimeout(l),l=setTimeout(u,this.I)})),r.on("message",(async(t,s)=>{clearTimeout(l),l=setTimeout(u,this.I);try{if(s)throw new Error("Binary messages are not supported");const a=String(t);if("P"===a)return void r.send("p");if("x"===a){if(1!==i)throw new Error("Unexpected close ack message");return i=2,void n()}if(2===i)throw new Error("Unexpected message after close ack");const c=function(t){const e=JSON.parse(t);if("object"!=typeof e||!e||Array.isArray(e))throw new Error("Must specify change and optional id");const{id:s,change:r}=e;if(void 0===s)return{change:r};if("number"!=typeof s)throw new Error("if specified, id must be a number");return{change:r,id:s}}(a);e.beginTransaction();try{await o.send(c.change,c.id)}finally{e.endTransaction()}}catch(t){r.send(JSON.stringify({error:t instanceof Error?t.message:"Internal error"}))}})),this.closing)return e.sendError(503,1012),void h("server shutdown");r.send(JSON.stringify({init:o.getInitialData()})),o.listen(((t,e)=>{const s=void 0!==e?{id:e,...t}:t;r.send(JSON.stringify(s))}));let l=setTimeout(u,this.I);this.closers.add(a)}catch(t){a("communication",t),e.sendError(500),h("error")}}}}const w=(t,e,s)=>console.warn(`shared-reducer: ${e}`,s),m=t=>t;class y{k;q;validate;A;J;constructor(t,e,s,r={}){this.k=t,this.q=e,this.validate=s,this.A=r.readErrorIntercept??m,this.J=r.writeErrorIntercept??m}async read(t){try{return await this.k.get(this.q,t)}catch(t){throw this.A(t)}}async write(t,e,s){const r={};Object.entries(e).forEach((([t,e])=>{const i=t;e!==(Object.prototype.hasOwnProperty.call(s,i)?s[i]:void 0)&&(r[i]?Object.defineProperty(r,i,{value:e,configurable:!0,enumerable:!0,writable:!0}):r[i]=e)}));try{await this.k.update(this.q,t,r)}catch(t){throw this.J(t)}}}class f extends Error{}class p{read=this.get;validate;M=new Map;constructor(t=t=>t){this.validate=t}set(t,e){this.M.set(t,e)}get(t){return this.M.get(t)}delete(t){this.M.delete(t)}write(t,e,s){if(s!==this.M.get(t))throw new Error("Unexpected previous value");this.M.set(t,e)}}const g="Cannot modify data",v={validateWriteSpec(){throw new f(g)},validateWrite(){throw new f(g)}};class _{N;constructor(t=[]){this.N=t}validateWrite(t,e){for(const s of this.N){const r=Object.prototype.hasOwnProperty.call(e,s),i=Object.prototype.hasOwnProperty.call(t,s);if(r!==i)throw new f(r?`Cannot remove field ${String(s)}`:`Cannot add field ${String(s)}`);if(i&&e[s]!==t[s])throw new f(`Cannot edit field ${String(s)}`)}}}export{s as AsyncTaskQueue,o as Broadcaster,u as CLOSE,l as CLOSE_ACK,y as CollectionStorageModel,p as InMemoryModel,n as InMemoryTopic,c as PING,h as PONG,f as PermissionError,v as ReadOnly,a as ReadWrite,_ as ReadWriteStruct,r as TaskQueueMap,i as TrackingTopicMap,e as UniqueIdProvider,d as WebsocketHandlerFactory};
@@ -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"===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;
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"===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};
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.2",
3
+ "version": "5.1.0",
4
4
  "description": "shared state management",
5
5
  "author": "David Evans",
6
6
  "license": "MIT",
@@ -45,16 +45,16 @@
45
45
  "homepage": "https://github.com/davidje13/shared-reducer#readme",
46
46
  "devDependencies": {
47
47
  "@rollup/plugin-terser": "0.4.x",
48
- "@rollup/plugin-typescript": "11.x",
48
+ "@rollup/plugin-typescript": "12.x",
49
49
  "collection-storage": "3.x",
50
50
  "json-immutability-helper": "4.x",
51
51
  "lean-test": "2.x",
52
- "prettier": "3.3.3",
52
+ "prettier": "3.4.2",
53
53
  "rollup": "4.x",
54
54
  "rollup-plugin-dts": "6.x",
55
55
  "superwstest": "2.x",
56
- "tslib": "2.7.x",
57
- "typescript": "5.6.x",
56
+ "tslib": "2.8.x",
57
+ "typescript": "5.7.x",
58
58
  "websocket-express": "3.x"
59
59
  }
60
60
  }