@iadev93/zuno 0.0.6 → 0.0.8
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/dist/{index-IbvF6TBr.d.cts → index-BIwJxy6r.d.cts} +17 -3
- package/dist/{index-IbvF6TBr.d.ts → index-BIwJxy6r.d.ts} +17 -3
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +3 -3
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +24 -24
- package/dist/server/index.d.ts +24 -24
- package/dist/server/index.js +3 -3
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
type ConflictResolver<T = unknown> = (localState: T, serverState: T, storeKey: string) => T;
|
|
1
2
|
/**
|
|
2
3
|
* Authoritative state event.
|
|
3
4
|
*/
|
|
4
5
|
type ZunoStateEvent = {
|
|
5
6
|
storeKey: string;
|
|
6
|
-
state:
|
|
7
|
+
state: unknown;
|
|
7
8
|
version?: number;
|
|
8
9
|
baseVersion?: number;
|
|
9
10
|
origin?: string;
|
|
@@ -44,6 +45,8 @@ type SSEOptions = {
|
|
|
44
45
|
getLastEventId: () => number;
|
|
45
46
|
onOpen?: () => void;
|
|
46
47
|
onClose?: () => void;
|
|
48
|
+
onEvent?: (event: ZunoStateEvent) => void;
|
|
49
|
+
resolveConflict?: ConflictResolver;
|
|
47
50
|
};
|
|
48
51
|
declare function startSSE(opts: SSEOptions): ZunoTransport;
|
|
49
52
|
type BCOptions = {
|
|
@@ -94,6 +97,13 @@ interface Universe {
|
|
|
94
97
|
clear(): void;
|
|
95
98
|
hydrateSnapshot(snapshot: ZunoSnapshot): void;
|
|
96
99
|
}
|
|
100
|
+
type Dispatch = (event: ZunoStateEvent) => Promise<TransportStatus>;
|
|
101
|
+
type MiddlewareAPI = {
|
|
102
|
+
universe: Universe;
|
|
103
|
+
clientId: string;
|
|
104
|
+
versions: Map<string, number>;
|
|
105
|
+
};
|
|
106
|
+
type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch;
|
|
97
107
|
/**
|
|
98
108
|
* Options for creating a Zuno instance.
|
|
99
109
|
*/
|
|
@@ -110,6 +120,10 @@ type CreateZunoOptions = {
|
|
|
110
120
|
optimistic?: boolean;
|
|
111
121
|
/** Unique client identifier (default: random UUID). */
|
|
112
122
|
clientId?: string;
|
|
123
|
+
/** Middleware chain. */
|
|
124
|
+
middleware?: Middleware[];
|
|
125
|
+
/** Optional function to resolve 409 conflicts. */
|
|
126
|
+
resolveConflict?: ConflictResolver;
|
|
113
127
|
};
|
|
114
128
|
/**
|
|
115
129
|
* An extended interface for a Zuno store that includes methods for setting state
|
|
@@ -141,11 +155,11 @@ declare const createZuno: (opts?: CreateZunoOptions) => {
|
|
|
141
155
|
get: <T>(key: string, init?: () => T) => T;
|
|
142
156
|
set: <T>(key: string, next: T | ((prev: T) => T), init?: () => T) => Promise<TransportStatus>;
|
|
143
157
|
subscribe: <T>(key: string, init: () => T, cb: (state: T) => void) => () => void;
|
|
144
|
-
dispatch:
|
|
158
|
+
dispatch: Dispatch;
|
|
145
159
|
stop: () => void;
|
|
146
160
|
hydrateSnapshot: (snapshot: ZunoSnapshot) => void;
|
|
147
161
|
getLastEventId: () => number;
|
|
148
162
|
setLastEventId: (id: number) => void;
|
|
149
163
|
};
|
|
150
164
|
|
|
151
|
-
export { type BoundStore as B, type CreateZunoOptions as C, type Store as S, type TransportStatus as T, type Universe as U, type ZunoSnapshot as Z,
|
|
165
|
+
export { type BoundStore as B, type CreateZunoOptions as C, type Dispatch as D, type Middleware as M, type Store as S, type TransportStatus as T, type Universe as U, type ZunoSnapshot as Z, type MiddlewareAPI as a, createUniverse as b, createStore as c, createZuno as d, type ConflictResolver as e, type ZunoStateEvent as f, type ZunoTransport as g, applyIncomingEvent as h, startSSE as i, startBroadcastChannel as s };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
type ConflictResolver<T = unknown> = (localState: T, serverState: T, storeKey: string) => T;
|
|
1
2
|
/**
|
|
2
3
|
* Authoritative state event.
|
|
3
4
|
*/
|
|
4
5
|
type ZunoStateEvent = {
|
|
5
6
|
storeKey: string;
|
|
6
|
-
state:
|
|
7
|
+
state: unknown;
|
|
7
8
|
version?: number;
|
|
8
9
|
baseVersion?: number;
|
|
9
10
|
origin?: string;
|
|
@@ -44,6 +45,8 @@ type SSEOptions = {
|
|
|
44
45
|
getLastEventId: () => number;
|
|
45
46
|
onOpen?: () => void;
|
|
46
47
|
onClose?: () => void;
|
|
48
|
+
onEvent?: (event: ZunoStateEvent) => void;
|
|
49
|
+
resolveConflict?: ConflictResolver;
|
|
47
50
|
};
|
|
48
51
|
declare function startSSE(opts: SSEOptions): ZunoTransport;
|
|
49
52
|
type BCOptions = {
|
|
@@ -94,6 +97,13 @@ interface Universe {
|
|
|
94
97
|
clear(): void;
|
|
95
98
|
hydrateSnapshot(snapshot: ZunoSnapshot): void;
|
|
96
99
|
}
|
|
100
|
+
type Dispatch = (event: ZunoStateEvent) => Promise<TransportStatus>;
|
|
101
|
+
type MiddlewareAPI = {
|
|
102
|
+
universe: Universe;
|
|
103
|
+
clientId: string;
|
|
104
|
+
versions: Map<string, number>;
|
|
105
|
+
};
|
|
106
|
+
type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch;
|
|
97
107
|
/**
|
|
98
108
|
* Options for creating a Zuno instance.
|
|
99
109
|
*/
|
|
@@ -110,6 +120,10 @@ type CreateZunoOptions = {
|
|
|
110
120
|
optimistic?: boolean;
|
|
111
121
|
/** Unique client identifier (default: random UUID). */
|
|
112
122
|
clientId?: string;
|
|
123
|
+
/** Middleware chain. */
|
|
124
|
+
middleware?: Middleware[];
|
|
125
|
+
/** Optional function to resolve 409 conflicts. */
|
|
126
|
+
resolveConflict?: ConflictResolver;
|
|
113
127
|
};
|
|
114
128
|
/**
|
|
115
129
|
* An extended interface for a Zuno store that includes methods for setting state
|
|
@@ -141,11 +155,11 @@ declare const createZuno: (opts?: CreateZunoOptions) => {
|
|
|
141
155
|
get: <T>(key: string, init?: () => T) => T;
|
|
142
156
|
set: <T>(key: string, next: T | ((prev: T) => T), init?: () => T) => Promise<TransportStatus>;
|
|
143
157
|
subscribe: <T>(key: string, init: () => T, cb: (state: T) => void) => () => void;
|
|
144
|
-
dispatch:
|
|
158
|
+
dispatch: Dispatch;
|
|
145
159
|
stop: () => void;
|
|
146
160
|
hydrateSnapshot: (snapshot: ZunoSnapshot) => void;
|
|
147
161
|
getLastEventId: () => number;
|
|
148
162
|
setLastEventId: (id: number) => void;
|
|
149
163
|
};
|
|
150
164
|
|
|
151
|
-
export { type BoundStore as B, type CreateZunoOptions as C, type Store as S, type TransportStatus as T, type Universe as U, type ZunoSnapshot as Z,
|
|
165
|
+
export { type BoundStore as B, type CreateZunoOptions as C, type Dispatch as D, type Middleware as M, type Store as S, type TransportStatus as T, type Universe as U, type ZunoSnapshot as Z, type MiddlewareAPI as a, createUniverse as b, createStore as c, createZuno as d, type ConflictResolver as e, type ZunoStateEvent as f, type ZunoTransport as g, applyIncomingEvent as h, startSSE as i, startBroadcastChannel as s };
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';function
|
|
1
|
+
'use strict';function I(s,u,o){let{clientId:n,versions:a}=o;if(u.origin!==n){if(typeof u.version=="number"){let d=a.get(u.storeKey)??0;if(u.origin!=="conflict-resolution"&&u.version<=d)return;a.set(u.storeKey,u.version);}s.getStore(u.storeKey,()=>u.state).set(u.state);}}function k(s){let{url:u,syncUrl:o,universe:n,clientId:a,versions:d,getLastEventId:y,onEvent:T,resolveConflict:v}=s,S=null,h=0,E=e=>{if(T)T(e);else {if(typeof e.version=="number"){let p=d.get(e.storeKey)??0;if(e.version<=p)return;d.set(e.storeKey,e.version);}n.getStore(e.storeKey,()=>e.state).set(e.state);}},f=[],Z=false;async function t(){if(Z||f.length===0||typeof navigator<"u"&&!navigator.onLine)return;Z=true;let e=new Map,p=[];for(let c of f)if(e.has(c.storeKey)){let r=e.get(c.storeKey),g=p[r];p[r]={...c,baseVersion:g.baseVersion};}else e.set(c.storeKey,p.length),p.push(c);f.length=0,f.push(...p);try{for(;f.length>0;){let c=f[0];try{let r=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)});if(!r.ok&&r.status!==409){if(r.status>=400&&r.status<500){f.shift();continue}f.shift();continue}if(r.status===409){let g=await r.json();if(g.current){let{state:m,version:b}=g.current,w=m;if(v){let R=n.getStore(c.storeKey,()=>null).get();w=v(R,m,c.storeKey);}if(E({storeKey:c.storeKey,state:w,version:b,origin:"conflict-resolution"}),JSON.stringify(w)!==JSON.stringify(m)){f.unshift({...c,state:w,baseVersion:b});continue}}f.shift();}else if(r.ok){let g=await r.json();g.event&&typeof g.event.version=="number"&&d.set(c.storeKey,g.event.version),f.shift();}else f.shift();}catch(r){console.error("[Zuno] Flush failed, retrying later",r);break}}}finally{Z=false;}}function l(){let e=y(),p=new URL(u,globalThis.location?.href);e>0&&p.searchParams.set("lastEventId",String(e)),S=new EventSource(p.toString()),S.addEventListener("snapshot",c=>{try{let r=JSON.parse(c.data);for(let[g,m]of Object.entries(r)){let b=m;E({storeKey:g,state:b.state,version:b.version,origin:"server"});}}catch(r){console.error("[Zuno] Failed to parse snapshot",r);}}),S.addEventListener("state",c=>{try{let r=JSON.parse(c.data);if(r.origin===a)return;E(r);}catch(r){console.error("[Zuno] Failed to parse SSE event",r);}}),S.onopen=()=>{h=0,s.onOpen?.(),t();},S.onerror=()=>{S?.close(),s.onClose?.();let c=Math.min(1e3*2**h,3e4);h++,setTimeout(l,c);};}typeof window<"u"&&window.addEventListener("online",t),l();let i=async e=>{try{if(s.optimistic&&n.getStore(e.storeKey,()=>e.state).set(e.state),typeof navigator<"u"&&!navigator.onLine){f.push(e);let r=d.get(e.storeKey)??0;return d.set(e.storeKey,r+1),{ok:!1,status:0,json:null,reason:"OFFLINE_QUEUED"}}let p=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(p.status===409){let r=await p.json();if(r.current){let{state:g,version:m}=r.current,b=g;if(v){let w=n.getStore(e.storeKey,()=>null).get();b=v(w,g,e.storeKey);}if(E({storeKey:e.storeKey,state:b,version:m,origin:"conflict-resolution"}),JSON.stringify(b)!==JSON.stringify(g))return await i({...e,state:b,baseVersion:m})}return {ok:!1,status:409,json:r,reason:"CONFLICT"}}if(!p.ok)return {ok:!1,status:p.status,json:await p.json()};let c=await p.json();if(c.event){let{state:r,version:g}=c.event;typeof g=="number"&&d.set(e.storeKey,g);}return {ok:!0,status:200,json:c}}catch(p){console.warn("[Zuno] Dispatch failed, queuing",p),f.push(e);let c=d.get(e.storeKey)??0;return d.set(e.storeKey,c+1),setTimeout(t,1e3),{ok:false,status:500,json:p,reason:"NETWORK_ERROR_QUEUED"}}};return {dispatch:i,unsubscribe:()=>{S?.close(),typeof window<"u"&&window.removeEventListener("online",t);}}}function x(s){let{channelName:u,clientId:o,onEvent:n,getSnapshot:a,onSnapshot:d}=s,y=new BroadcastChannel(u);return y.onmessage=T=>{let v=T.data;v.origin!==o&&(v.type==="event"&&n(v.event),v.type==="hello"&&y.postMessage({type:"snapshot",snapshot:a(),origin:o}),v.type==="snapshot"&&d(v.snapshot));},{publish:T=>y.postMessage({type:"event",event:T,origin:o}),hello:()=>y.postMessage({type:"hello",origin:o}),stop:()=>y.close()}}var K=s=>{let u=s,o=new Set;return {get:()=>u,set:n=>{let a=typeof n=="function"?n(u):n;Object.is(a,u)||(u=a,o.forEach(d=>{d(u);}));},subscribe:n=>(o.add(n),()=>o.delete(n))}},O=()=>{let s=new Map;return {getStore(o,n){return s.has(o)||s.set(o,K(n())),s.get(o)},snapshot(){let o={};for(let[n,a]of s.entries())o[n]=a.get();return o},restore(o){for(let[n,a]of Object.entries(o)){let d=s.get(n);d?d.set(a):s.set(n,K(a));}},delete(o){s.delete(o);},clear(){s.clear();},hydrateSnapshot(o){let n={};for(let[a,d]of Object.entries(o.state))n[a]=d.state;this.restore(n);}}},U=(s={})=>{let o=new Map,n=s.universe??O(),a=s.clientId??globalThis.crypto?.randomUUID?.()??String(Math.random()),y=0;function T(t){let l={};for(let[i,e]of Object.entries(t.state))l[i]=e.state,o.set(i,e.version);n.restore(l),y=t.lastEventId;}let v=t=>{typeof t.eventId=="number"&&(y=Math.max(y,t.eventId)),I(n,t,{clientId:a,versions:o});},S=s.sseUrl&&s.syncUrl?k({universe:n,url:s.sseUrl,syncUrl:s.syncUrl,optimistic:s.optimistic??true,clientId:a,versions:o,getLastEventId:()=>y,onOpen:()=>{},onClose:()=>{},onEvent:t=>f(t),resolveConflict:s.resolveConflict}):null,h=s.channelName?x({channelName:s.channelName,clientId:a,onEvent:t=>f(t),getSnapshot:()=>{let t=n.snapshot(),l={};for(let[i,e]of Object.entries(t))l[i]={state:e,version:o.get(i)??0};return l},onSnapshot:t=>{for(let[l,i]of Object.entries(t))v({storeKey:l,state:i.state,version:i.version});}}):null;setTimeout(()=>h?.hello(),0);let E=async t=>{if(t.origin&&t.origin!==a)return v(t),{ok:true,status:200,json:null};if(S){let i=await S.dispatch({...t,origin:a,baseVersion:o.get(t.storeKey)??0});if((i.reason==="OFFLINE_QUEUED"||i.reason==="NETWORK_ERROR_QUEUED")&&h){let e=o.get(t.storeKey)??0;h.publish({...t,version:e,origin:a});}return i}let l=(o.get(t.storeKey)??0)+1;return v({...t,version:l}),o.set(t.storeKey,l),h&&h.publish({...t,version:l,origin:a}),{ok:true,status:200,json:null}},f=E;if(s.middleware&&s.middleware.length>0){let t={universe:n,clientId:a,versions:o};f=s.middleware.map(i=>i(t)).reduceRight((i,e)=>e(i),E);}return {universe:n,clientId:a,store:(t,l)=>{let i=n.getStore(t,l);return {key:t,raw:()=>i,get:()=>i.get(),subscribe:e=>i.subscribe(e),set:e=>{let p=i.get(),c=typeof e=="function"?e(p):e;return f({storeKey:t,state:c})}}},getStore:n.getStore.bind(n),get:(t,l)=>n.getStore(t,l??(()=>{})).get(),set:async(t,l,i)=>{let e=n.getStore(t,i??(()=>{})),p=typeof l=="function"?l(e.get()):l;return f({storeKey:t,state:p})},subscribe:(t,l,i)=>n.getStore(t,l).subscribe(i),dispatch:f,stop:()=>{S?.unsubscribe?.(),h?.stop?.();},hydrateSnapshot:T,getLastEventId:()=>y,setLastEventId:t=>{y=t;}}};function C(s){return {getSnapshot:()=>s.get(),subscribe:u=>s.subscribe(()=>u())}}exports.applyIncomingEvent=I;exports.createStore=K;exports.createUniverse=O;exports.createZuno=U;exports.startBroadcastChannel=x;exports.startSSE=k;exports.toReadable=C;//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sync/index.ts","../src/core/index.ts","../src/shared/readable.ts"],"names":["applyIncomingEvent","universe","event","context","clientId","versions","current","startSSE","opts","url","syncUrl","getLastEventId","es","retryCount","connect","lastId","connectUrl","e","snap","key","rec","r","err","delay","res","data","state","version","startBroadcastChannel","channelName","onEvent","getSnapshot","onSnapshot","channel","msg","createStore","initial","listeners","next","value","l","listener","createUniverse","stores","init","out","store","existing","snapshot","plain","k","createZuno","sseReady","lastEventId","hydrateSnapshot","apply","sse","bc","storeKey","dispatch","nextVersion","rawStore","cb","prev","s","id","toReadable","onChange"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Universe } from \"../core\";\n\n// --- Types ---\n\n/**\n * Authoritative state event.\n */\nexport type ZunoStateEvent = {\n storeKey: string;\n state: any;\n version?: number;\n baseVersion?: number;\n origin?: string;\n ts?: number;\n eventId?: number;\n};\n\n/**\n * Generic transport status.\n */\nexport type TransportStatus = {\n ok: boolean;\n status: number;\n json: any;\n reason?: string;\n};\n\n/**\n * Client transport interface.\n */\nexport interface ZunoTransport {\n dispatch(event: ZunoStateEvent): Promise<TransportStatus>;\n unsubscribe?(): void;\n}\n\n/**\n * Apply incoming event to the universe and local bookkeeping.\n */\nexport function applyIncomingEvent(\n universe: Universe,\n event: ZunoStateEvent,\n context: {\n clientId: string;\n localState: Map<string, unknown>;\n versions: Map<string, number>;\n }\n) {\n const { clientId, versions } = context;\n\n // 1. Loopback suppression\n if (event.origin === clientId) return;\n\n // 2. Version check (if provided by transport)\n if (typeof event.version === \"number\") {\n const current = versions.get(event.storeKey) ?? 0;\n if (event.version <= current) return; // Stale\n versions.set(event.storeKey, event.version);\n }\n\n // 3. Apply to universe\n universe.getStore(event.storeKey, () => event.state).set(event.state);\n}\n\n// --- SSE Client ---\n\nexport type SSEOptions = {\n universe: Universe;\n url: string;\n syncUrl: string;\n optimistic: boolean;\n clientId: string;\n versions: Map<string, number>;\n getLastEventId: () => number;\n onOpen?: () => void;\n onClose?: () => void;\n};\n\nexport function startSSE(opts: SSEOptions): ZunoTransport {\n const { url, syncUrl, universe, clientId, versions, getLastEventId } = opts;\n let es: EventSource | null = null;\n let retryCount = 0;\n\n function connect() {\n const lastId = getLastEventId();\n const connectUrl = new URL(url, globalThis.location?.href);\n if (lastId > 0) connectUrl.searchParams.set(\"lastEventId\", String(lastId));\n\n es = new EventSource(connectUrl.toString());\n\n es.addEventListener(\"snapshot\", (e: any) => {\n try {\n const snap = JSON.parse(e.data);\n for (const [key, rec] of Object.entries(snap)) {\n const r = rec as { state: any; version: number };\n versions.set(key, Math.max(versions.get(key) ?? 0, r.version));\n universe.getStore(key, () => r.state).set(r.state);\n }\n } catch (err) {\n console.error(\"[Zuno] Failed to parse snapshot\", err);\n }\n });\n\n es.addEventListener(\"state\", (e: any) => {\n try {\n const event = JSON.parse(e.data) as ZunoStateEvent;\n if (event.origin === clientId) return;\n\n if (typeof event.version === \"number\") {\n const current = versions.get(event.storeKey) ?? 0;\n if (event.version <= current) return;\n versions.set(event.storeKey, event.version);\n }\n\n universe.getStore(event.storeKey, () => event.state).set(event.state);\n } catch (err) {\n console.error(\"[Zuno] Failed to parse SSE event\", err);\n }\n });\n\n es.onopen = () => {\n retryCount = 0;\n opts.onOpen?.();\n };\n\n es.onerror = () => {\n es?.close();\n opts.onClose?.();\n const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);\n retryCount++;\n setTimeout(connect, delay);\n };\n }\n\n connect();\n\n return {\n dispatch: async (event) => {\n try {\n const res = await fetch(syncUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(event),\n });\n\n if (res.status === 409) {\n const data = await res.json();\n if (data.current) {\n const { state, version } = data.current;\n versions.set(event.storeKey, version);\n universe.getStore(event.storeKey, () => state).set(state);\n }\n return { ok: false, status: 409, json: data, reason: \"CONFLICT\" };\n }\n\n if (!res.ok) return { ok: false, status: res.status, json: await res.json() };\n\n return { ok: true, status: 200, json: await res.json() };\n } catch (err) {\n return { ok: false, status: 500, json: err, reason: \"NETWORK_ERROR\" };\n }\n },\n unsubscribe: () => {\n es?.close();\n },\n };\n}\n\n// --- BroadcastChannel ---\n\nexport type BCOptions = {\n channelName: string;\n clientId: string;\n onEvent: (event: ZunoStateEvent) => void;\n getSnapshot: () => Record<string, { state: unknown; version: number }>;\n onSnapshot: (snap: Record<string, { state: unknown; version: number }>) => void;\n};\n\nexport function startBroadcastChannel(opts: BCOptions) {\n const { channelName, clientId, onEvent, getSnapshot, onSnapshot } = opts;\n const channel = new BroadcastChannel(channelName);\n\n channel.onmessage = (e) => {\n const msg = e.data;\n if (msg.origin === clientId) return;\n\n if (msg.type === \"event\") onEvent(msg.event);\n if (msg.type === \"hello\") channel.postMessage({ type: \"snapshot\", snapshot: getSnapshot(), origin: clientId });\n if (msg.type === \"snapshot\") onSnapshot(msg.snapshot);\n };\n\n return {\n publish: (event: ZunoStateEvent) => channel.postMessage({ type: \"event\", event, origin: clientId }),\n hello: () => channel.postMessage({ type: \"hello\", origin: clientId }),\n stop: () => channel.close(),\n };\n}\n","import { startSSE, startBroadcastChannel, applyIncomingEvent } from \"../sync\";\nimport type { ZunoStateEvent } from \"../sync\";\n\n// --- Types ---\n\n/**\n * Authoritative snapshot of the entire universe.\n */\nexport type ZunoSnapshot = {\n state: Record<string, { state: unknown; version: number }>;\n lastEventId: number;\n};\n\n/**\n * A simple state container for a single keyed value.\n */\nexport interface Store<T> {\n get(): T;\n set(next: T | ((prev: T) => T)): void;\n subscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * A Universe manages many stores.\n */\nexport interface Universe {\n getStore<T>(key: string, init: () => T): Store<T>;\n snapshot(): Record<string, unknown>;\n restore(data: Record<string, unknown>): void;\n delete(key: string): void;\n clear(): void;\n hydrateSnapshot(snapshot: ZunoSnapshot): void;\n}\n\n/**\n * Options for creating a Zuno instance.\n */\nexport type CreateZunoOptions = {\n /** Optional pre-existing universe. */\n universe?: Universe;\n /** BroadcastChannel name for local tab sync. */\n channelName?: string;\n /** SSE endpoint URL. */\n sseUrl?: string;\n /** Sync endpoint URL (required if sseUrl is provided). */\n syncUrl?: string;\n /** Apply updates locally before server confirmation (default: true). */\n optimistic?: boolean;\n /** Unique client identifier (default: random UUID). */\n clientId?: string;\n};\n\n/**\n * An extended interface for a Zuno store that includes methods for setting state\n * and a unique key identifier. This represents a store that has been \"bound\" or registered.\n */\nexport type BoundStore<T> = {\n key: string;\n get: () => T;\n set: (next: T | ((prev: T) => T)) => Promise<any>;\n subscribe: (cb: (state: T) => void) => () => void;\n raw: () => Store<T>;\n};\n\n// --- Store Implementation ---\n\n/**\n * Creates a raw ZUNO state management store.\n */\nexport const createStore = <T>(initial: T): Store<T> => {\n let state = initial;\n const listeners = new Set<(state: T) => void>();\n\n return {\n get: () => state,\n set: (next) => {\n const value = typeof next === \"function\" ? (next as (prev: T) => T)(state) : next;\n if (Object.is(value, state)) return;\n state = value;\n listeners.forEach((l) => l(state));\n },\n subscribe: (listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n};\n\n// --- Universe Implementation ---\n\n/**\n * Creates a ZUNO Universe to manage multiple stores.\n */\nexport const createUniverse = (): Universe => {\n const stores = new Map<string, Store<any>>();\n\n const universe: Universe = {\n getStore<T>(key: string, init: () => T): Store<T> {\n if (!stores.has(key)) {\n stores.set(key, createStore(init()));\n }\n return stores.get(key)! as Store<T>;\n },\n snapshot(): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, store] of stores.entries()) {\n out[key] = store.get();\n }\n return out;\n },\n restore(data: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(data)) {\n const existing = stores.get(key);\n if (existing) {\n existing.set(value as any);\n } else {\n stores.set(key, createStore(value as any));\n }\n }\n },\n delete(key: string): void {\n stores.delete(key);\n },\n clear(): void {\n stores.clear();\n },\n hydrateSnapshot(snapshot: ZunoSnapshot) {\n const plain: Record<string, unknown> = {};\n for (const [k, rec] of Object.entries(snapshot.state)) {\n plain[k] = rec.state;\n }\n this.restore(plain);\n },\n };\n\n return universe;\n};\n\n// --- Main Zuno Factory ---\n\n/**\n * Creates a Zuno instance for distributed state synchronization.\n */\nexport const createZuno = (opts: CreateZunoOptions = {}) => {\n const localState = new Map<string, unknown>();\n const versions = new Map<string, number>();\n const universe = opts.universe ?? createUniverse();\n const clientId = opts.clientId ?? (globalThis.crypto?.randomUUID?.() ?? String(Math.random()));\n let sseReady = false;\n let lastEventId = 0;\n\n function hydrateSnapshot(snapshot: ZunoSnapshot) {\n const plain: Record<string, unknown> = {};\n for (const [k, rec] of Object.entries(snapshot.state)) {\n plain[k] = rec.state;\n versions.set(k, rec.version);\n }\n universe.restore(plain);\n lastEventId = snapshot.lastEventId;\n }\n\n const apply = (event: ZunoStateEvent) => {\n if (typeof event.eventId === \"number\") {\n lastEventId = Math.max(lastEventId, event.eventId);\n }\n applyIncomingEvent(universe, event, { clientId, localState, versions });\n };\n\n const sse = opts.sseUrl && opts.syncUrl\n ? startSSE({\n universe,\n url: opts.sseUrl,\n syncUrl: opts.syncUrl,\n optimistic: opts.optimistic ?? true,\n clientId,\n versions,\n getLastEventId: () => lastEventId,\n onOpen: () => { sseReady = true; },\n onClose: () => { sseReady = false; },\n })\n : null;\n\n const bc = opts.channelName\n ? startBroadcastChannel({\n channelName: opts.channelName,\n clientId,\n onEvent: apply,\n getSnapshot: () => {\n const snap = universe.snapshot();\n const out: Record<string, { state: unknown; version: number }> = {};\n for (const [storeKey, state] of Object.entries(snap)) {\n out[storeKey] = { state, version: versions.get(storeKey) ?? 0 };\n }\n return out;\n },\n onSnapshot: (snap) => {\n for (const [storeKey, rec] of Object.entries(snap)) {\n apply({ storeKey, state: rec.state, version: rec.version });\n }\n },\n })\n : null;\n\n setTimeout(() => bc?.hello(), 0);\n\n const dispatch = async (event: ZunoStateEvent) => {\n if (sse && sseReady) {\n return await sse.dispatch({\n ...event,\n origin: clientId,\n baseVersion: versions.get(event.storeKey) ?? 0,\n });\n }\n\n const nextVersion = (versions.get(event.storeKey) ?? 0) + 1;\n apply({ ...event, version: nextVersion });\n versions.set(event.storeKey, nextVersion);\n\n if (bc) {\n bc.publish({ ...event, version: nextVersion, origin: clientId });\n }\n\n return { ok: true, status: 200, json: null };\n };\n\n const store = <T,>(storeKey: string, init: () => T): BoundStore<T> => {\n const rawStore = universe.getStore<T>(storeKey, init);\n return {\n key: storeKey,\n raw: () => rawStore,\n get: () => rawStore.get(),\n subscribe: (cb) => rawStore.subscribe(cb),\n set: (next) => {\n const prev = rawStore.get();\n const state = typeof next === \"function\" ? (next as (prev: T) => T)(prev) : next;\n return dispatch({ storeKey, state });\n },\n };\n };\n\n return {\n universe,\n clientId,\n store,\n getStore: universe.getStore.bind(universe),\n get: <T,>(key: string, init?: () => T) => universe.getStore<T>(key, init ?? (() => undefined as any)).get(),\n set: async <T,>(key: string, next: T | ((prev: T) => T), init?: () => T) => {\n const s = universe.getStore<T>(key, init ?? (() => undefined as any));\n const state = typeof next === \"function\" ? (next as (prev: T) => T)(s.get()) : next;\n return dispatch({ storeKey: key, state });\n },\n subscribe: <T,>(key: string, init: () => T, cb: (state: T) => void) => universe.getStore<T>(key, init).subscribe(cb),\n dispatch,\n stop: () => {\n sse?.unsubscribe?.();\n bc?.stop?.();\n },\n hydrateSnapshot,\n getLastEventId: () => lastEventId,\n setLastEventId: (id: number) => { lastEventId = id; },\n };\n};\n","/** Universal UI adapter contract */\nexport type ZunoReadable<T> = {\n /** Read current value (sync) */\n getSnapshot(): T;\n\n /**\n * Subscribe to changes.\n * Call `onChange()` whenever snapshot may have changed.\n * Return unsubscribe.\n */\n subscribe(onChange: () => void): () => void;\n\n /** Optional: React SSR (server snapshot) */\n getServerSnapshot?: () => T;\n};\n\n/** Minimal store shape that can be adapted into a readable */\nexport type ZunoSubscribableStore<T> = {\n get(): T;\n subscribe(cb: (state: T) => void): () => void;\n};\n\n/** Adapter helper: convert store => readable */\nexport function toReadable<T>(store: ZunoSubscribableStore<T>): ZunoReadable<T> {\n return {\n getSnapshot: () => store.get(),\n subscribe: (onChange) => store.subscribe(() => onChange()),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/sync/index.ts","../src/core/index.ts","../src/shared/readable.ts"],"names":["applyIncomingEvent","universe","event","context","clientId","versions","current","startSSE","opts","url","syncUrl","getLastEventId","onEvent","resolveConflict","es","retryCount","applyState","queue","isFlushing","flushQueue","keyIndex","reducedQueue","idx","prev","res","data","serverState","serverVersion","nextState","localState","json","err","connect","lastId","connectUrl","e","snap","key","rec","r","delay","dispatchFn","currentV","state","version","startBroadcastChannel","channelName","getSnapshot","onSnapshot","channel","msg","createStore","initial","listeners","next","value","l","listener","createUniverse","stores","init","out","store","existing","snapshot","plain","k","createZuno","lastEventId","hydrateSnapshot","apply","sse","dispatch","bc","storeKey","coreDispatch","v","nextVersion","middlewareAPI","middleware","rawStore","cb","s","id","toReadable","onChange"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Universe } from \"../core\";\n\n// --- Types ---\n\nexport type ConflictResolver<T = unknown> = (\n\tlocalState: T,\n\tserverState: T,\n\tstoreKey: string,\n) => T;\n\n/**\n * Authoritative state event.\n */\nexport type ZunoStateEvent = {\n\tstoreKey: string;\n\tstate: unknown; // Was 'any', now strictly 'unknown'\n\tversion?: number;\n\tbaseVersion?: number;\n\torigin?: string;\n\tts?: number;\n\teventId?: number;\n};\n\n/**\n * Generic transport status.\n */\nexport type TransportStatus = {\n\tok: boolean;\n\tstatus: number;\n\tjson: any;\n\treason?: string;\n};\n\n/**\n * Client transport interface.\n */\nexport interface ZunoTransport {\n\tdispatch(event: ZunoStateEvent): Promise<TransportStatus>;\n\tunsubscribe?(): void;\n}\n\n/**\n * Apply incoming event to the universe and local bookkeeping.\n */\nexport function applyIncomingEvent(\n\tuniverse: Universe,\n\tevent: ZunoStateEvent,\n\tcontext: {\n\t\tclientId: string;\n\t\tlocalState: Map<string, unknown>;\n\t\tversions: Map<string, number>;\n\t},\n) {\n\tconst { clientId, versions } = context;\n\n\t// 1. Loopback suppression\n\tif (event.origin === clientId) return;\n\n\t// 2. Version check (if provided by transport)\n\tif (typeof event.version === \"number\") {\n\t\tconst current = versions.get(event.storeKey) ?? 0;\n\t\tif (event.origin !== \"conflict-resolution\" && event.version <= current)\n\t\t\treturn; // Stale check\n\n\t\tversions.set(event.storeKey, event.version);\n\t}\n\n\t// 3. Apply to universe\n\tuniverse.getStore(event.storeKey, () => event.state).set(event.state);\n}\n\n// --- SSE Client ---\n\nexport type SSEOptions = {\n\tuniverse: Universe;\n\turl: string;\n\tsyncUrl: string;\n\toptimistic: boolean;\n\tclientId: string;\n\tversions: Map<string, number>;\n\tgetLastEventId: () => number;\n\tonOpen?: () => void;\n\tonClose?: () => void;\n\tonEvent?: (event: ZunoStateEvent) => void;\n\tresolveConflict?: ConflictResolver;\n};\n\nexport function startSSE(opts: SSEOptions): ZunoTransport {\n\tconst {\n\t\turl,\n\t\tsyncUrl,\n\t\tuniverse,\n\t\tclientId,\n\t\tversions,\n\t\tgetLastEventId,\n\t\tonEvent,\n\t\tresolveConflict,\n\t} = opts;\n\tlet es: EventSource | null = null;\n\tlet retryCount = 0;\n\n\t// Helper to apply state changes\n\tconst applyState = (event: ZunoStateEvent) => {\n\t\tif (onEvent) {\n\t\t\tonEvent(event);\n\t\t} else {\n\t\t\t// Fallback default implementation\n\t\t\tif (typeof event.version === \"number\") {\n\t\t\t\tconst current = versions.get(event.storeKey) ?? 0;\n\t\t\t\tif (event.version <= current) return;\n\t\t\t\tversions.set(event.storeKey, event.version);\n\t\t\t}\n\t\t\tuniverse.getStore(event.storeKey, () => event.state).set(event.state);\n\t\t}\n\t};\n\n\t// --- Offline Support ---\n\tconst queue: ZunoStateEvent[] = [];\n\tlet isFlushing = false;\n\n\tasync function flushQueue() {\n\t\tif (isFlushing || queue.length === 0) return;\n\t\tif (typeof navigator !== \"undefined\" && !navigator.onLine) return;\n\n\t\tisFlushing = true;\n\n\t\t// --- Coalesce / Deduplicate Logic ---\n\t\t// 1. Map index of first occurrence of each storeKey\n\t\tconst keyIndex = new Map<string, number>();\n\t\t// 2. Reduced queue construction\n\t\tconst reducedQueue: ZunoStateEvent[] = [];\n\n\t\tfor (const event of queue) {\n\t\t\tif (keyIndex.has(event.storeKey)) {\n\t\t\t\t// We've seen this key before. We want to update the existing entry in reducedQueue.\n\t\t\t\tconst idx = keyIndex.get(event.storeKey)!;\n\t\t\t\tconst prev = reducedQueue[idx];\n\t\t\t\t// Merge: keep original baseVersion (from the start of the chain) but use NEW state.\n\t\t\t\t// Note: We also likely want to keep the original 'ts' if strictly ordering,\n\t\t\t\t// but state is what matters.\n\t\t\t\t// The 'version' in the event is the *optimistic* version.\n\t\t\t\t// We can keep the *latest* optimistic version (e.g. v10) in the event,\n\t\t\t\t// but server will likely only verify baseVersion.\n\t\t\t\treducedQueue[idx] = { ...event, baseVersion: prev.baseVersion };\n\t\t\t} else {\n\t\t\t\tkeyIndex.set(event.storeKey, reducedQueue.length);\n\t\t\t\treducedQueue.push(event);\n\t\t\t}\n\t\t}\n\n\t\t// Replace original queue with reduced one.\n\t\t// We modify 'queue' in place or reset it.\n\t\t// Since 'queue' is const binding to array, we can't reassign variable,\n\t\t// but we can clear and push.\n\t\tqueue.length = 0;\n\t\tqueue.push(...reducedQueue);\n\n\t\ttry {\n\t\t\twhile (queue.length > 0) {\n\t\t\t\tconst event = queue[0];\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(syncUrl, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\tbody: JSON.stringify(event),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!res.ok && res.status !== 409) {\n\t\t\t\t\t\t// Keep in queue for retry if it's a transient server error?\n\t\t\t\t\t\t// For now we dequeue on non-network errors to avoid blocking.\n\t\t\t\t\t\tif (res.status >= 400 && res.status < 500) {\n\t\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// For 500, we might want to retry? Let's treat it as network-ish for now.\n\t\t\t\t\t\t// But to be safe and not block forever:\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (res.status === 409) {\n\t\t\t\t\t\tconst data = await res.json();\n\t\t\t\t\t\tif (data.current) {\n\t\t\t\t\t\t\tconst { state: serverState, version: serverVersion } =\n\t\t\t\t\t\t\t\tdata.current;\n\n\t\t\t\t\t\t\tlet nextState = serverState;\n\t\t\t\t\t\t\tif (resolveConflict) {\n\t\t\t\t\t\t\t\tconst localState = universe\n\t\t\t\t\t\t\t\t\t.getStore(event.storeKey, () => null)\n\t\t\t\t\t\t\t\t\t.get();\n\t\t\t\t\t\t\t\tnextState = resolveConflict(\n\t\t\t\t\t\t\t\t\tlocalState,\n\t\t\t\t\t\t\t\t\tserverState,\n\t\t\t\t\t\t\t\t\tevent.storeKey,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// versions.set(event.storeKey, serverVersion); // REMOVE THIS\n\n\t\t\t\t\t\t\t// 2. Apply resolved state locally\n\t\t\t\t\t\t\tapplyState({\n\t\t\t\t\t\t\t\tstoreKey: event.storeKey,\n\t\t\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\t\t\tversion: serverVersion,\n\t\t\t\t\t\t\t\torigin: \"conflict-resolution\",\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// 3. If resolved state differs from server, auto-sync back\n\t\t\t\t\t\t\tif (JSON.stringify(nextState) !== JSON.stringify(serverState)) {\n\t\t\t\t\t\t\t\tqueue.unshift({\n\t\t\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\t\t\t\tbaseVersion: serverVersion,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t} else if (res.ok) {\n\t\t\t\t\t\tconst json = await res.json();\n\t\t\t\t\t\tif (json.event && typeof json.event.version === \"number\") {\n\t\t\t\t\t\t\t// Just update version map\n\t\t\t\t\t\t\tversions.set(event.storeKey, json.event.version);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(\"[Zuno] Flush failed, retrying later\", err);\n\t\t\t\t\tbreak; // Network error, stop flushing\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tisFlushing = false;\n\t\t}\n\t}\n\tfunction connect() {\n\t\tconst lastId = getLastEventId();\n\t\tconst connectUrl = new URL(url, globalThis.location?.href);\n\t\tif (lastId > 0) connectUrl.searchParams.set(\"lastEventId\", String(lastId));\n\n\t\tes = new EventSource(connectUrl.toString());\n\n\t\tes.addEventListener(\"snapshot\", (e: any) => {\n\t\t\ttry {\n\t\t\t\tconst snap = JSON.parse(e.data);\n\t\t\t\tfor (const [key, rec] of Object.entries(snap)) {\n\t\t\t\t\tconst r = rec as { state: any; version: number };\n\t\t\t\t\tapplyState({\n\t\t\t\t\t\tstoreKey: key,\n\t\t\t\t\t\tstate: r.state,\n\t\t\t\t\t\tversion: r.version,\n\t\t\t\t\t\torigin: \"server\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[Zuno] Failed to parse snapshot\", err);\n\t\t\t}\n\t\t});\n\n\t\tes.addEventListener(\"state\", (e: any) => {\n\t\t\ttry {\n\t\t\t\tconst event = JSON.parse(e.data) as ZunoStateEvent;\n\t\t\t\t// Loopback suppression logic is now handled by caller (middleware/core) if onEvent is provided,\n\t\t\t\t// OR we should keep it here?\n\t\t\t\t// If we dispatch to middleware, the middleware might log it.\n\t\t\t\t// But if it's our own event, we shouldn't re-apply it if we already did optimally?\n\t\t\t\t// Actually, startSSE optimistic logic does it.\n\t\t\t\t// If we receive an echo, we might want to update version but not state if it matches?\n\t\t\t\t// Let's pass it to applyState.\n\n\t\t\t\tif (event.origin === clientId) return;\n\n\t\t\t\tapplyState(event);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[Zuno] Failed to parse SSE event\", err);\n\t\t\t}\n\t\t});\n\n\t\tes.onopen = () => {\n\t\t\tretryCount = 0;\n\t\t\topts.onOpen?.();\n\t\t\tflushQueue();\n\t\t};\n\n\t\tes.onerror = () => {\n\t\t\tes?.close();\n\t\t\topts.onClose?.();\n\t\t\tconst delay = Math.min(1000 * 2 ** retryCount, 30000);\n\t\t\tretryCount++;\n\t\t\tsetTimeout(connect, delay);\n\t\t};\n\t}\n\n\tif (typeof window !== \"undefined\") {\n\t\twindow.addEventListener(\"online\", flushQueue);\n\t}\n\n\tconnect();\n\n\tconst dispatchFn = async (\n\t\tevent: ZunoStateEvent,\n\t): Promise<TransportStatus> => {\n\t\ttry {\n\t\t\tif (opts.optimistic) {\n\t\t\t\t// Manually apply optimistic update (local origin)\n\t\t\t\t// We can call applyState here OR let middleware do it if we pass it through middleware?\n\t\t\t\t// If we pass through middleware, the middleware calls dispatch.\n\t\t\t\t// This IS the dispatch.\n\t\t\t\t// So we should apply it here.\n\t\t\t\t// BUT if we want middleware to run on *incoming*, we are separating the two flows.\n\t\t\t\t// For outgoing, we apply locally.\n\t\t\t\t// Note: if user logs, they see the action.\n\t\t\t\t// We don't need to call applyState for outgoing if startSSE is responsible for transport only?\n\t\t\t\t// BUT startSSE manages local versions map.\n\n\t\t\t\tuniverse.getStore(event.storeKey, () => event.state).set(event.state);\n\t\t\t}\n\n\t\t\t// Check online status first\n\t\t\tif (typeof navigator !== \"undefined\" && !navigator.onLine) {\n\t\t\t\tqueue.push(event);\n\t\t\t\t// Optimistically increment version so next event uses correct baseVersion\n\t\t\t\tconst currentV = versions.get(event.storeKey) ?? 0;\n\t\t\t\tversions.set(event.storeKey, currentV + 1);\n\t\t\t\treturn { ok: false, status: 0, json: null, reason: \"OFFLINE_QUEUED\" };\n\t\t\t}\n\n\t\t\tconst res = await fetch(syncUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify(event),\n\t\t\t});\n\n\t\t\tif (res.status === 409) {\n\t\t\t\tconst data = await res.json();\n\t\t\t\tif (data.current) {\n\t\t\t\t\tconst { state: serverState, version: serverVersion } = data.current;\n\n\t\t\t\t\tlet nextState = serverState;\n\t\t\t\t\tif (resolveConflict) {\n\t\t\t\t\t\tconst localState = universe\n\t\t\t\t\t\t\t.getStore(event.storeKey, () => null)\n\t\t\t\t\t\t\t.get();\n\t\t\t\t\t\tnextState = resolveConflict(\n\t\t\t\t\t\t\tlocalState,\n\t\t\t\t\t\t\tserverState,\n\t\t\t\t\t\t\tevent.storeKey,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// versions.set(event.storeKey, serverVersion); // REMOVE THIS: applyState handles it\n\t\t\t\t\tapplyState({\n\t\t\t\t\t\tstoreKey: event.storeKey,\n\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\tversion: serverVersion,\n\t\t\t\t\t\torigin: \"conflict-resolution\",\n\t\t\t\t\t});\n\n\t\t\t\t\tif (JSON.stringify(nextState) !== JSON.stringify(serverState)) {\n\t\t\t\t\t\t// Return special status to indicate a merge happened and we should retry?\n\t\t\t\t\t\t// Or just fire new fetch?\n\t\t\t\t\t\t// Let's fire new fetch recursively by calling dispatch (but careful about infinite loop if 409 happens again).\n\t\t\t\t\t\t// Or easier:\n\n\t\t\t\t\t\treturn await dispatchFn({\n\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\t\tbaseVersion: serverVersion,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { ok: false, status: 409, json: data, reason: \"CONFLICT\" };\n\t\t\t}\n\n\t\t\tif (!res.ok)\n\t\t\t\treturn { ok: false, status: res.status, json: await res.json() };\n\n\t\t\tconst json = await res.json();\n\t\t\tif (json.event) {\n\t\t\t\tconst { state, version } = json.event;\n\t\t\t\tif (typeof version === \"number\") {\n\t\t\t\t\tversions.set(event.storeKey, version);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { ok: true, status: 200, json };\n\t\t} catch (err) {\n\t\t\t// Network failure catch\n\t\t\tconsole.warn(\"[Zuno] Dispatch failed, queuing\", err);\n\t\t\tqueue.push(event);\n\t\t\t// Optimistically increment version here too, assuming the previous one is \"pending\"\n\t\t\t// and subsequent edits should build on top of it.\n\t\t\tconst currentV = versions.get(event.storeKey) ?? 0;\n\t\t\tversions.set(event.storeKey, currentV + 1);\n\n\t\t\tsetTimeout(flushQueue, 1000);\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tstatus: 500,\n\t\t\t\tjson: err,\n\t\t\t\treason: \"NETWORK_ERROR_QUEUED\",\n\t\t\t};\n\t\t}\n\t};\n\n\treturn {\n\t\tdispatch: dispatchFn,\n\t\tunsubscribe: () => {\n\t\t\tes?.close();\n\t\t\tif (typeof window !== \"undefined\") {\n\t\t\t\twindow.removeEventListener(\"online\", flushQueue);\n\t\t\t}\n\t\t},\n\t};\n}\n\n// --- BroadcastChannel ---\n\nexport type BCOptions = {\n\tchannelName: string;\n\tclientId: string;\n\tonEvent: (event: ZunoStateEvent) => void;\n\tgetSnapshot: () => Record<string, { state: unknown; version: number }>;\n\tonSnapshot: (\n\t\tsnap: Record<string, { state: unknown; version: number }>,\n\t) => void;\n};\n\nexport function startBroadcastChannel(opts: BCOptions) {\n\tconst { channelName, clientId, onEvent, getSnapshot, onSnapshot } = opts;\n\tconst channel = new BroadcastChannel(channelName);\n\n\tchannel.onmessage = (e) => {\n\t\tconst msg = e.data;\n\t\tif (msg.origin === clientId) return;\n\n\t\tif (msg.type === \"event\") onEvent(msg.event);\n\t\tif (msg.type === \"hello\")\n\t\t\tchannel.postMessage({\n\t\t\t\ttype: \"snapshot\",\n\t\t\t\tsnapshot: getSnapshot(),\n\t\t\t\torigin: clientId,\n\t\t\t});\n\t\tif (msg.type === \"snapshot\") onSnapshot(msg.snapshot);\n\t};\n\n\treturn {\n\t\tpublish: (event: ZunoStateEvent) =>\n\t\t\tchannel.postMessage({ type: \"event\", event, origin: clientId }),\n\t\thello: () => channel.postMessage({ type: \"hello\", origin: clientId }),\n\t\tstop: () => channel.close(),\n\t};\n}\n","import type {\n\tConflictResolver,\n\tTransportStatus,\n\tZunoStateEvent,\n} from \"../sync\";\nimport { applyIncomingEvent, startBroadcastChannel, startSSE } from \"../sync\";\n\n// --- Types ---\n\n/**\n * Authoritative snapshot of the entire universe.\n */\nexport type ZunoSnapshot = {\n\tstate: Record<string, { state: unknown; version: number }>;\n\tlastEventId: number;\n};\n\n/**\n * A simple state container for a single keyed value.\n */\nexport interface Store<T> {\n\tget(): T;\n\tset(next: T | ((prev: T) => T)): void;\n\tsubscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * A Universe manages many stores.\n */\nexport interface Universe {\n\tgetStore<T>(key: string, init: () => T): Store<T>;\n\tsnapshot(): Record<string, unknown>;\n\trestore(data: Record<string, unknown>): void;\n\tdelete(key: string): void;\n\tclear(): void;\n\thydrateSnapshot(snapshot: ZunoSnapshot): void;\n}\n\n// --- Middleware Types ---\n\nexport type Dispatch = (event: ZunoStateEvent) => Promise<TransportStatus>;\n\nexport type MiddlewareAPI = {\n\tuniverse: Universe;\n\tclientId: string;\n\tversions: Map<string, number>;\n};\n\nexport type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch;\n\n/**\n * Options for creating a Zuno instance.\n */\nexport type CreateZunoOptions = {\n\t/** Optional pre-existing universe. */\n\tuniverse?: Universe;\n\t/** BroadcastChannel name for local tab sync. */\n\tchannelName?: string;\n\t/** SSE endpoint URL. */\n\tsseUrl?: string;\n\t/** Sync endpoint URL (required if sseUrl is provided). */\n\tsyncUrl?: string;\n\t/** Apply updates locally before server confirmation (default: true). */\n\toptimistic?: boolean;\n\t/** Unique client identifier (default: random UUID). */\n\tclientId?: string;\n\t/** Middleware chain. */\n\tmiddleware?: Middleware[];\n\t/** Optional function to resolve 409 conflicts. */\n\tresolveConflict?: ConflictResolver;\n};\n\n/**\n * An extended interface for a Zuno store that includes methods for setting state\n * and a unique key identifier. This represents a store that has been \"bound\" or registered.\n */\nexport type BoundStore<T> = {\n\tkey: string;\n\tget: () => T;\n\tset: (next: T | ((prev: T) => T)) => Promise<any>;\n\tsubscribe: (cb: (state: T) => void) => () => void;\n\traw: () => Store<T>;\n};\n\n// --- Store Implementation ---\n\n/**\n * Creates a raw ZUNO state management store.\n */\nexport const createStore = <T>(initial: T): Store<T> => {\n\tlet state = initial;\n\tconst listeners = new Set<(state: T) => void>();\n\n\treturn {\n\t\tget: () => state,\n\t\tset: (next) => {\n\t\t\tconst value =\n\t\t\t\ttypeof next === \"function\" ? (next as (prev: T) => T)(state) : next;\n\t\t\tif (Object.is(value, state)) return;\n\t\t\tstate = value;\n\t\t\tlisteners.forEach((l) => {\n\t\t\t\tl(state);\n\t\t\t});\n\t\t},\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\t};\n};\n\n// --- Universe Implementation ---\n\n/**\n * Creates a ZUNO Universe to manage multiple stores.\n */\nexport const createUniverse = (): Universe => {\n\tconst stores = new Map<string, Store<any>>();\n\n\tconst universe: Universe = {\n\t\tgetStore<T>(key: string, init: () => T): Store<T> {\n\t\t\tif (!stores.has(key)) {\n\t\t\t\tstores.set(key, createStore(init()));\n\t\t\t}\n\t\t\treturn stores.get(key)! as Store<T>;\n\t\t},\n\t\tsnapshot(): Record<string, unknown> {\n\t\t\tconst out: Record<string, unknown> = {};\n\t\t\tfor (const [key, store] of stores.entries()) {\n\t\t\t\tout[key] = store.get();\n\t\t\t}\n\t\t\treturn out;\n\t\t},\n\t\trestore(data: Record<string, unknown>): void {\n\t\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\t\tconst existing = stores.get(key);\n\t\t\t\tif (existing) {\n\t\t\t\t\texisting.set(value as any);\n\t\t\t\t} else {\n\t\t\t\t\tstores.set(key, createStore(value as any));\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdelete(key: string): void {\n\t\t\tstores.delete(key);\n\t\t},\n\t\tclear(): void {\n\t\t\tstores.clear();\n\t\t},\n\t\thydrateSnapshot(snapshot: ZunoSnapshot) {\n\t\t\tconst plain: Record<string, unknown> = {};\n\t\t\tfor (const [k, rec] of Object.entries(snapshot.state)) {\n\t\t\t\tplain[k] = rec.state;\n\t\t\t}\n\t\t\tthis.restore(plain);\n\t\t},\n\t};\n\n\treturn universe;\n};\n\n// --- Main Zuno Factory ---\n\n/**\n * Creates a Zuno instance for distributed state synchronization.\n */\nexport const createZuno = (opts: CreateZunoOptions = {}) => {\n\tconst localState = new Map<string, unknown>();\n\tconst versions = new Map<string, number>();\n\tconst universe = opts.universe ?? createUniverse();\n\tconst clientId =\n\t\topts.clientId ?? globalThis.crypto?.randomUUID?.() ?? String(Math.random());\n\tlet sseReady = false;\n\tlet lastEventId = 0;\n\n\tfunction hydrateSnapshot(snapshot: ZunoSnapshot) {\n\t\tconst plain: Record<string, unknown> = {};\n\t\tfor (const [k, rec] of Object.entries(snapshot.state)) {\n\t\t\tplain[k] = rec.state;\n\t\t\tversions.set(k, rec.version);\n\t\t}\n\t\tuniverse.restore(plain);\n\t\tlastEventId = snapshot.lastEventId;\n\t}\n\n\tconst apply = (event: ZunoStateEvent) => {\n\t\tif (typeof event.eventId === \"number\") {\n\t\t\tlastEventId = Math.max(lastEventId, event.eventId);\n\t\t}\n\t\tapplyIncomingEvent(universe, event, { clientId, localState, versions });\n\t};\n\n\tconst sse =\n\t\topts.sseUrl && opts.syncUrl\n\t\t\t? startSSE({\n\t\t\t\t\tuniverse,\n\t\t\t\t\turl: opts.sseUrl,\n\t\t\t\t\tsyncUrl: opts.syncUrl,\n\t\t\t\t\toptimistic: opts.optimistic ?? true,\n\t\t\t\t\tclientId,\n\t\t\t\t\tversions,\n\t\t\t\t\tgetLastEventId: () => lastEventId,\n\t\t\t\t\tonOpen: () => {\n\t\t\t\t\t\tsseReady = true;\n\t\t\t\t\t},\n\t\t\t\t\tonClose: () => {\n\t\t\t\t\t\tsseReady = false;\n\t\t\t\t\t},\n\t\t\t\t\tonEvent: (e) => dispatch(e), // Route incoming SSE events through middleware\n\t\t\t\t\tresolveConflict: opts.resolveConflict,\n\t\t\t\t})\n\t\t\t: null;\n\n\tconst bc = opts.channelName\n\t\t? startBroadcastChannel({\n\t\t\t\tchannelName: opts.channelName,\n\t\t\t\tclientId,\n\t\t\t\tonEvent: (e) => dispatch(e), // Route incoming BC events through middleware\n\t\t\t\tgetSnapshot: () => {\n\t\t\t\t\tconst snap = universe.snapshot();\n\t\t\t\t\tconst out: Record<string, { state: unknown; version: number }> = {};\n\t\t\t\t\tfor (const [storeKey, state] of Object.entries(snap)) {\n\t\t\t\t\t\tout[storeKey] = { state, version: versions.get(storeKey) ?? 0 };\n\t\t\t\t\t}\n\t\t\t\t\treturn out;\n\t\t\t\t},\n\t\t\t\tonSnapshot: (snap) => {\n\t\t\t\t\tfor (const [storeKey, rec] of Object.entries(snap)) {\n\t\t\t\t\t\tapply({ storeKey, state: rec.state, version: rec.version });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t})\n\t\t: null;\n\n\tsetTimeout(() => bc?.hello(), 0);\n\n\tconst coreDispatch = async (\n\t\tevent: ZunoStateEvent,\n\t): Promise<TransportStatus> => {\n\t\t// 1. Incoming Event (from Server or other Tab via BC)\n\t\tif (event.origin && event.origin !== clientId) {\n\t\t\tapply(event);\n\t\t\t// Incoming events don't need to be re-broadcasted to network or BC typically,\n\t\t\t// unless acting as a relay (not current design).\n\t\t\treturn { ok: true, status: 200, json: null };\n\t\t}\n\n\t\t// 2. Outgoing Event (Local Action)\n\t\tif (sse) {\n\t\t\tconst res = await sse.dispatch({\n\t\t\t\t...event,\n\t\t\t\torigin: clientId,\n\t\t\t\tbaseVersion: versions.get(event.storeKey) ?? 0,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\t(res.reason === \"OFFLINE_QUEUED\" ||\n\t\t\t\t\tres.reason === \"NETWORK_ERROR_QUEUED\") &&\n\t\t\t\tbc\n\t\t\t) {\n\t\t\t\tconst v = versions.get(event.storeKey) ?? 0;\n\t\t\t\tbc.publish({ ...event, version: v, origin: clientId });\n\t\t\t}\n\n\t\t\treturn res;\n\t\t}\n\n\t\tconst nextVersion = (versions.get(event.storeKey) ?? 0) + 1;\n\t\tapply({ ...event, version: nextVersion });\n\t\tversions.set(event.storeKey, nextVersion);\n\n\t\tif (bc) {\n\t\t\tbc.publish({ ...event, version: nextVersion, origin: clientId });\n\t\t}\n\n\t\treturn { ok: true, status: 200, json: null };\n\t};\n\n\t// --- Middleware Composition ---\n\tlet dispatch: Dispatch = coreDispatch;\n\n\tif (opts.middleware && opts.middleware.length > 0) {\n\t\tconst middlewareAPI: MiddlewareAPI = {\n\t\t\tuniverse,\n\t\t\tclientId,\n\t\t\tversions,\n\t\t};\n\t\tconst chain = opts.middleware.map((middleware) =>\n\t\t\tmiddleware(middlewareAPI),\n\t\t);\n\t\tdispatch = chain.reduceRight(\n\t\t\t(next, middleware) => middleware(next),\n\t\t\tcoreDispatch,\n\t\t);\n\t}\n\n\tconst store = <T>(storeKey: string, init: () => T): BoundStore<T> => {\n\t\tconst rawStore = universe.getStore<T>(storeKey, init);\n\t\treturn {\n\t\t\tkey: storeKey,\n\t\t\traw: () => rawStore,\n\t\t\tget: () => rawStore.get(),\n\t\t\tsubscribe: (cb) => rawStore.subscribe(cb),\n\t\t\tset: (next) => {\n\t\t\t\tconst prev = rawStore.get();\n\t\t\t\tconst state =\n\t\t\t\t\ttypeof next === \"function\" ? (next as (prev: T) => T)(prev) : next;\n\t\t\t\treturn dispatch({ storeKey, state });\n\t\t\t},\n\t\t};\n\t};\n\n\treturn {\n\t\tuniverse,\n\t\tclientId,\n\t\tstore,\n\t\tgetStore: universe.getStore.bind(universe),\n\t\tget: <T>(key: string, init?: () => T) =>\n\t\t\tuniverse.getStore<T>(key, init ?? (() => undefined as any)).get(),\n\t\tset: async <T>(key: string, next: T | ((prev: T) => T), init?: () => T) => {\n\t\t\tconst s = universe.getStore<T>(key, init ?? (() => undefined as any));\n\t\t\tconst state =\n\t\t\t\ttypeof next === \"function\" ? (next as (prev: T) => T)(s.get()) : next;\n\t\t\treturn dispatch({ storeKey: key, state });\n\t\t},\n\t\tsubscribe: <T>(key: string, init: () => T, cb: (state: T) => void) =>\n\t\t\tuniverse.getStore<T>(key, init).subscribe(cb),\n\t\tdispatch,\n\t\tstop: () => {\n\t\t\tsse?.unsubscribe?.();\n\t\t\tbc?.stop?.();\n\t\t},\n\t\thydrateSnapshot,\n\t\tgetLastEventId: () => lastEventId,\n\t\tsetLastEventId: (id: number) => {\n\t\t\tlastEventId = id;\n\t\t},\n\t};\n};\n","/** Universal UI adapter contract */\nexport type ZunoReadable<T> = {\n\t/** Read current value (sync) */\n\tgetSnapshot(): T;\n\n\t/**\n\t * Subscribe to changes.\n\t * Call `onChange()` whenever snapshot may have changed.\n\t * Return unsubscribe.\n\t */\n\tsubscribe(onChange: () => void): () => void;\n\n\t/** Optional: React SSR (server snapshot) */\n\tgetServerSnapshot?: () => T;\n};\n\n/** Minimal store shape that can be adapted into a readable */\nexport type ZunoSubscribableStore<T> = {\n\tget(): T;\n\tsubscribe(cb: (state: T) => void): () => void;\n};\n\n/** Adapter helper: convert store => readable */\nexport function toReadable<T>(\n\tstore: ZunoSubscribableStore<T>,\n): ZunoReadable<T> {\n\treturn {\n\t\tgetSnapshot: () => store.get(),\n\t\tsubscribe: (onChange) => store.subscribe(() => onChange()),\n\t};\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BoundStore, C as CreateZunoOptions, S as Store, T as TransportStatus, U as Universe, Z as ZunoSnapshot, f as ZunoStateEvent, g as ZunoTransport,
|
|
1
|
+
export { B as BoundStore, e as ConflictResolver, C as CreateZunoOptions, D as Dispatch, M as Middleware, a as MiddlewareAPI, S as Store, T as TransportStatus, U as Universe, Z as ZunoSnapshot, f as ZunoStateEvent, g as ZunoTransport, h as applyIncomingEvent, c as createStore, b as createUniverse, d as createZuno, s as startBroadcastChannel, i as startSSE } from './index-BIwJxy6r.cjs';
|
|
2
2
|
|
|
3
3
|
/** Universal UI adapter contract */
|
|
4
4
|
type ZunoReadable<T> = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BoundStore, C as CreateZunoOptions, S as Store, T as TransportStatus, U as Universe, Z as ZunoSnapshot, f as ZunoStateEvent, g as ZunoTransport,
|
|
1
|
+
export { B as BoundStore, e as ConflictResolver, C as CreateZunoOptions, D as Dispatch, M as Middleware, a as MiddlewareAPI, S as Store, T as TransportStatus, U as Universe, Z as ZunoSnapshot, f as ZunoStateEvent, g as ZunoTransport, h as applyIncomingEvent, c as createStore, b as createUniverse, d as createZuno, s as startBroadcastChannel, i as startSSE } from './index-BIwJxy6r.js';
|
|
2
2
|
|
|
3
3
|
/** Universal UI adapter contract */
|
|
4
4
|
type ZunoReadable<T> = {
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function
|
|
1
|
+
function I(s,u,o){let{clientId:n,versions:a}=o;if(u.origin!==n){if(typeof u.version=="number"){let d=a.get(u.storeKey)??0;if(u.origin!=="conflict-resolution"&&u.version<=d)return;a.set(u.storeKey,u.version);}s.getStore(u.storeKey,()=>u.state).set(u.state);}}function k(s){let{url:u,syncUrl:o,universe:n,clientId:a,versions:d,getLastEventId:y,onEvent:T,resolveConflict:v}=s,S=null,h=0,E=e=>{if(T)T(e);else {if(typeof e.version=="number"){let p=d.get(e.storeKey)??0;if(e.version<=p)return;d.set(e.storeKey,e.version);}n.getStore(e.storeKey,()=>e.state).set(e.state);}},f=[],Z=false;async function t(){if(Z||f.length===0||typeof navigator<"u"&&!navigator.onLine)return;Z=true;let e=new Map,p=[];for(let c of f)if(e.has(c.storeKey)){let r=e.get(c.storeKey),g=p[r];p[r]={...c,baseVersion:g.baseVersion};}else e.set(c.storeKey,p.length),p.push(c);f.length=0,f.push(...p);try{for(;f.length>0;){let c=f[0];try{let r=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)});if(!r.ok&&r.status!==409){if(r.status>=400&&r.status<500){f.shift();continue}f.shift();continue}if(r.status===409){let g=await r.json();if(g.current){let{state:m,version:b}=g.current,w=m;if(v){let R=n.getStore(c.storeKey,()=>null).get();w=v(R,m,c.storeKey);}if(E({storeKey:c.storeKey,state:w,version:b,origin:"conflict-resolution"}),JSON.stringify(w)!==JSON.stringify(m)){f.unshift({...c,state:w,baseVersion:b});continue}}f.shift();}else if(r.ok){let g=await r.json();g.event&&typeof g.event.version=="number"&&d.set(c.storeKey,g.event.version),f.shift();}else f.shift();}catch(r){console.error("[Zuno] Flush failed, retrying later",r);break}}}finally{Z=false;}}function l(){let e=y(),p=new URL(u,globalThis.location?.href);e>0&&p.searchParams.set("lastEventId",String(e)),S=new EventSource(p.toString()),S.addEventListener("snapshot",c=>{try{let r=JSON.parse(c.data);for(let[g,m]of Object.entries(r)){let b=m;E({storeKey:g,state:b.state,version:b.version,origin:"server"});}}catch(r){console.error("[Zuno] Failed to parse snapshot",r);}}),S.addEventListener("state",c=>{try{let r=JSON.parse(c.data);if(r.origin===a)return;E(r);}catch(r){console.error("[Zuno] Failed to parse SSE event",r);}}),S.onopen=()=>{h=0,s.onOpen?.(),t();},S.onerror=()=>{S?.close(),s.onClose?.();let c=Math.min(1e3*2**h,3e4);h++,setTimeout(l,c);};}typeof window<"u"&&window.addEventListener("online",t),l();let i=async e=>{try{if(s.optimistic&&n.getStore(e.storeKey,()=>e.state).set(e.state),typeof navigator<"u"&&!navigator.onLine){f.push(e);let r=d.get(e.storeKey)??0;return d.set(e.storeKey,r+1),{ok:!1,status:0,json:null,reason:"OFFLINE_QUEUED"}}let p=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(p.status===409){let r=await p.json();if(r.current){let{state:g,version:m}=r.current,b=g;if(v){let w=n.getStore(e.storeKey,()=>null).get();b=v(w,g,e.storeKey);}if(E({storeKey:e.storeKey,state:b,version:m,origin:"conflict-resolution"}),JSON.stringify(b)!==JSON.stringify(g))return await i({...e,state:b,baseVersion:m})}return {ok:!1,status:409,json:r,reason:"CONFLICT"}}if(!p.ok)return {ok:!1,status:p.status,json:await p.json()};let c=await p.json();if(c.event){let{state:r,version:g}=c.event;typeof g=="number"&&d.set(e.storeKey,g);}return {ok:!0,status:200,json:c}}catch(p){console.warn("[Zuno] Dispatch failed, queuing",p),f.push(e);let c=d.get(e.storeKey)??0;return d.set(e.storeKey,c+1),setTimeout(t,1e3),{ok:false,status:500,json:p,reason:"NETWORK_ERROR_QUEUED"}}};return {dispatch:i,unsubscribe:()=>{S?.close(),typeof window<"u"&&window.removeEventListener("online",t);}}}function x(s){let{channelName:u,clientId:o,onEvent:n,getSnapshot:a,onSnapshot:d}=s,y=new BroadcastChannel(u);return y.onmessage=T=>{let v=T.data;v.origin!==o&&(v.type==="event"&&n(v.event),v.type==="hello"&&y.postMessage({type:"snapshot",snapshot:a(),origin:o}),v.type==="snapshot"&&d(v.snapshot));},{publish:T=>y.postMessage({type:"event",event:T,origin:o}),hello:()=>y.postMessage({type:"hello",origin:o}),stop:()=>y.close()}}var K=s=>{let u=s,o=new Set;return {get:()=>u,set:n=>{let a=typeof n=="function"?n(u):n;Object.is(a,u)||(u=a,o.forEach(d=>{d(u);}));},subscribe:n=>(o.add(n),()=>o.delete(n))}},O=()=>{let s=new Map;return {getStore(o,n){return s.has(o)||s.set(o,K(n())),s.get(o)},snapshot(){let o={};for(let[n,a]of s.entries())o[n]=a.get();return o},restore(o){for(let[n,a]of Object.entries(o)){let d=s.get(n);d?d.set(a):s.set(n,K(a));}},delete(o){s.delete(o);},clear(){s.clear();},hydrateSnapshot(o){let n={};for(let[a,d]of Object.entries(o.state))n[a]=d.state;this.restore(n);}}},U=(s={})=>{let o=new Map,n=s.universe??O(),a=s.clientId??globalThis.crypto?.randomUUID?.()??String(Math.random()),y=0;function T(t){let l={};for(let[i,e]of Object.entries(t.state))l[i]=e.state,o.set(i,e.version);n.restore(l),y=t.lastEventId;}let v=t=>{typeof t.eventId=="number"&&(y=Math.max(y,t.eventId)),I(n,t,{clientId:a,versions:o});},S=s.sseUrl&&s.syncUrl?k({universe:n,url:s.sseUrl,syncUrl:s.syncUrl,optimistic:s.optimistic??true,clientId:a,versions:o,getLastEventId:()=>y,onOpen:()=>{},onClose:()=>{},onEvent:t=>f(t),resolveConflict:s.resolveConflict}):null,h=s.channelName?x({channelName:s.channelName,clientId:a,onEvent:t=>f(t),getSnapshot:()=>{let t=n.snapshot(),l={};for(let[i,e]of Object.entries(t))l[i]={state:e,version:o.get(i)??0};return l},onSnapshot:t=>{for(let[l,i]of Object.entries(t))v({storeKey:l,state:i.state,version:i.version});}}):null;setTimeout(()=>h?.hello(),0);let E=async t=>{if(t.origin&&t.origin!==a)return v(t),{ok:true,status:200,json:null};if(S){let i=await S.dispatch({...t,origin:a,baseVersion:o.get(t.storeKey)??0});if((i.reason==="OFFLINE_QUEUED"||i.reason==="NETWORK_ERROR_QUEUED")&&h){let e=o.get(t.storeKey)??0;h.publish({...t,version:e,origin:a});}return i}let l=(o.get(t.storeKey)??0)+1;return v({...t,version:l}),o.set(t.storeKey,l),h&&h.publish({...t,version:l,origin:a}),{ok:true,status:200,json:null}},f=E;if(s.middleware&&s.middleware.length>0){let t={universe:n,clientId:a,versions:o};f=s.middleware.map(i=>i(t)).reduceRight((i,e)=>e(i),E);}return {universe:n,clientId:a,store:(t,l)=>{let i=n.getStore(t,l);return {key:t,raw:()=>i,get:()=>i.get(),subscribe:e=>i.subscribe(e),set:e=>{let p=i.get(),c=typeof e=="function"?e(p):e;return f({storeKey:t,state:c})}}},getStore:n.getStore.bind(n),get:(t,l)=>n.getStore(t,l??(()=>{})).get(),set:async(t,l,i)=>{let e=n.getStore(t,i??(()=>{})),p=typeof l=="function"?l(e.get()):l;return f({storeKey:t,state:p})},subscribe:(t,l,i)=>n.getStore(t,l).subscribe(i),dispatch:f,stop:()=>{S?.unsubscribe?.(),h?.stop?.();},hydrateSnapshot:T,getLastEventId:()=>y,setLastEventId:t=>{y=t;}}};function C(s){return {getSnapshot:()=>s.get(),subscribe:u=>s.subscribe(()=>u())}}export{I as applyIncomingEvent,K as createStore,O as createUniverse,U as createZuno,x as startBroadcastChannel,k as startSSE,C as toReadable};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sync/index.ts","../src/core/index.ts","../src/shared/readable.ts"],"names":["applyIncomingEvent","universe","event","context","clientId","versions","current","startSSE","opts","url","syncUrl","getLastEventId","es","retryCount","connect","lastId","connectUrl","e","snap","key","rec","r","err","delay","res","data","state","version","startBroadcastChannel","channelName","onEvent","getSnapshot","onSnapshot","channel","msg","createStore","initial","listeners","next","value","l","listener","createUniverse","stores","init","out","store","existing","snapshot","plain","k","createZuno","sseReady","lastEventId","hydrateSnapshot","apply","sse","bc","storeKey","dispatch","nextVersion","rawStore","cb","prev","s","id","toReadable","onChange"],"mappings":"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","file":"index.js","sourcesContent":["import type { Universe } from \"../core\";\n\n// --- Types ---\n\n/**\n * Authoritative state event.\n */\nexport type ZunoStateEvent = {\n storeKey: string;\n state: any;\n version?: number;\n baseVersion?: number;\n origin?: string;\n ts?: number;\n eventId?: number;\n};\n\n/**\n * Generic transport status.\n */\nexport type TransportStatus = {\n ok: boolean;\n status: number;\n json: any;\n reason?: string;\n};\n\n/**\n * Client transport interface.\n */\nexport interface ZunoTransport {\n dispatch(event: ZunoStateEvent): Promise<TransportStatus>;\n unsubscribe?(): void;\n}\n\n/**\n * Apply incoming event to the universe and local bookkeeping.\n */\nexport function applyIncomingEvent(\n universe: Universe,\n event: ZunoStateEvent,\n context: {\n clientId: string;\n localState: Map<string, unknown>;\n versions: Map<string, number>;\n }\n) {\n const { clientId, versions } = context;\n\n // 1. Loopback suppression\n if (event.origin === clientId) return;\n\n // 2. Version check (if provided by transport)\n if (typeof event.version === \"number\") {\n const current = versions.get(event.storeKey) ?? 0;\n if (event.version <= current) return; // Stale\n versions.set(event.storeKey, event.version);\n }\n\n // 3. Apply to universe\n universe.getStore(event.storeKey, () => event.state).set(event.state);\n}\n\n// --- SSE Client ---\n\nexport type SSEOptions = {\n universe: Universe;\n url: string;\n syncUrl: string;\n optimistic: boolean;\n clientId: string;\n versions: Map<string, number>;\n getLastEventId: () => number;\n onOpen?: () => void;\n onClose?: () => void;\n};\n\nexport function startSSE(opts: SSEOptions): ZunoTransport {\n const { url, syncUrl, universe, clientId, versions, getLastEventId } = opts;\n let es: EventSource | null = null;\n let retryCount = 0;\n\n function connect() {\n const lastId = getLastEventId();\n const connectUrl = new URL(url, globalThis.location?.href);\n if (lastId > 0) connectUrl.searchParams.set(\"lastEventId\", String(lastId));\n\n es = new EventSource(connectUrl.toString());\n\n es.addEventListener(\"snapshot\", (e: any) => {\n try {\n const snap = JSON.parse(e.data);\n for (const [key, rec] of Object.entries(snap)) {\n const r = rec as { state: any; version: number };\n versions.set(key, Math.max(versions.get(key) ?? 0, r.version));\n universe.getStore(key, () => r.state).set(r.state);\n }\n } catch (err) {\n console.error(\"[Zuno] Failed to parse snapshot\", err);\n }\n });\n\n es.addEventListener(\"state\", (e: any) => {\n try {\n const event = JSON.parse(e.data) as ZunoStateEvent;\n if (event.origin === clientId) return;\n\n if (typeof event.version === \"number\") {\n const current = versions.get(event.storeKey) ?? 0;\n if (event.version <= current) return;\n versions.set(event.storeKey, event.version);\n }\n\n universe.getStore(event.storeKey, () => event.state).set(event.state);\n } catch (err) {\n console.error(\"[Zuno] Failed to parse SSE event\", err);\n }\n });\n\n es.onopen = () => {\n retryCount = 0;\n opts.onOpen?.();\n };\n\n es.onerror = () => {\n es?.close();\n opts.onClose?.();\n const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);\n retryCount++;\n setTimeout(connect, delay);\n };\n }\n\n connect();\n\n return {\n dispatch: async (event) => {\n try {\n const res = await fetch(syncUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(event),\n });\n\n if (res.status === 409) {\n const data = await res.json();\n if (data.current) {\n const { state, version } = data.current;\n versions.set(event.storeKey, version);\n universe.getStore(event.storeKey, () => state).set(state);\n }\n return { ok: false, status: 409, json: data, reason: \"CONFLICT\" };\n }\n\n if (!res.ok) return { ok: false, status: res.status, json: await res.json() };\n\n return { ok: true, status: 200, json: await res.json() };\n } catch (err) {\n return { ok: false, status: 500, json: err, reason: \"NETWORK_ERROR\" };\n }\n },\n unsubscribe: () => {\n es?.close();\n },\n };\n}\n\n// --- BroadcastChannel ---\n\nexport type BCOptions = {\n channelName: string;\n clientId: string;\n onEvent: (event: ZunoStateEvent) => void;\n getSnapshot: () => Record<string, { state: unknown; version: number }>;\n onSnapshot: (snap: Record<string, { state: unknown; version: number }>) => void;\n};\n\nexport function startBroadcastChannel(opts: BCOptions) {\n const { channelName, clientId, onEvent, getSnapshot, onSnapshot } = opts;\n const channel = new BroadcastChannel(channelName);\n\n channel.onmessage = (e) => {\n const msg = e.data;\n if (msg.origin === clientId) return;\n\n if (msg.type === \"event\") onEvent(msg.event);\n if (msg.type === \"hello\") channel.postMessage({ type: \"snapshot\", snapshot: getSnapshot(), origin: clientId });\n if (msg.type === \"snapshot\") onSnapshot(msg.snapshot);\n };\n\n return {\n publish: (event: ZunoStateEvent) => channel.postMessage({ type: \"event\", event, origin: clientId }),\n hello: () => channel.postMessage({ type: \"hello\", origin: clientId }),\n stop: () => channel.close(),\n };\n}\n","import { startSSE, startBroadcastChannel, applyIncomingEvent } from \"../sync\";\nimport type { ZunoStateEvent } from \"../sync\";\n\n// --- Types ---\n\n/**\n * Authoritative snapshot of the entire universe.\n */\nexport type ZunoSnapshot = {\n state: Record<string, { state: unknown; version: number }>;\n lastEventId: number;\n};\n\n/**\n * A simple state container for a single keyed value.\n */\nexport interface Store<T> {\n get(): T;\n set(next: T | ((prev: T) => T)): void;\n subscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * A Universe manages many stores.\n */\nexport interface Universe {\n getStore<T>(key: string, init: () => T): Store<T>;\n snapshot(): Record<string, unknown>;\n restore(data: Record<string, unknown>): void;\n delete(key: string): void;\n clear(): void;\n hydrateSnapshot(snapshot: ZunoSnapshot): void;\n}\n\n/**\n * Options for creating a Zuno instance.\n */\nexport type CreateZunoOptions = {\n /** Optional pre-existing universe. */\n universe?: Universe;\n /** BroadcastChannel name for local tab sync. */\n channelName?: string;\n /** SSE endpoint URL. */\n sseUrl?: string;\n /** Sync endpoint URL (required if sseUrl is provided). */\n syncUrl?: string;\n /** Apply updates locally before server confirmation (default: true). */\n optimistic?: boolean;\n /** Unique client identifier (default: random UUID). */\n clientId?: string;\n};\n\n/**\n * An extended interface for a Zuno store that includes methods for setting state\n * and a unique key identifier. This represents a store that has been \"bound\" or registered.\n */\nexport type BoundStore<T> = {\n key: string;\n get: () => T;\n set: (next: T | ((prev: T) => T)) => Promise<any>;\n subscribe: (cb: (state: T) => void) => () => void;\n raw: () => Store<T>;\n};\n\n// --- Store Implementation ---\n\n/**\n * Creates a raw ZUNO state management store.\n */\nexport const createStore = <T>(initial: T): Store<T> => {\n let state = initial;\n const listeners = new Set<(state: T) => void>();\n\n return {\n get: () => state,\n set: (next) => {\n const value = typeof next === \"function\" ? (next as (prev: T) => T)(state) : next;\n if (Object.is(value, state)) return;\n state = value;\n listeners.forEach((l) => l(state));\n },\n subscribe: (listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n};\n\n// --- Universe Implementation ---\n\n/**\n * Creates a ZUNO Universe to manage multiple stores.\n */\nexport const createUniverse = (): Universe => {\n const stores = new Map<string, Store<any>>();\n\n const universe: Universe = {\n getStore<T>(key: string, init: () => T): Store<T> {\n if (!stores.has(key)) {\n stores.set(key, createStore(init()));\n }\n return stores.get(key)! as Store<T>;\n },\n snapshot(): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, store] of stores.entries()) {\n out[key] = store.get();\n }\n return out;\n },\n restore(data: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(data)) {\n const existing = stores.get(key);\n if (existing) {\n existing.set(value as any);\n } else {\n stores.set(key, createStore(value as any));\n }\n }\n },\n delete(key: string): void {\n stores.delete(key);\n },\n clear(): void {\n stores.clear();\n },\n hydrateSnapshot(snapshot: ZunoSnapshot) {\n const plain: Record<string, unknown> = {};\n for (const [k, rec] of Object.entries(snapshot.state)) {\n plain[k] = rec.state;\n }\n this.restore(plain);\n },\n };\n\n return universe;\n};\n\n// --- Main Zuno Factory ---\n\n/**\n * Creates a Zuno instance for distributed state synchronization.\n */\nexport const createZuno = (opts: CreateZunoOptions = {}) => {\n const localState = new Map<string, unknown>();\n const versions = new Map<string, number>();\n const universe = opts.universe ?? createUniverse();\n const clientId = opts.clientId ?? (globalThis.crypto?.randomUUID?.() ?? String(Math.random()));\n let sseReady = false;\n let lastEventId = 0;\n\n function hydrateSnapshot(snapshot: ZunoSnapshot) {\n const plain: Record<string, unknown> = {};\n for (const [k, rec] of Object.entries(snapshot.state)) {\n plain[k] = rec.state;\n versions.set(k, rec.version);\n }\n universe.restore(plain);\n lastEventId = snapshot.lastEventId;\n }\n\n const apply = (event: ZunoStateEvent) => {\n if (typeof event.eventId === \"number\") {\n lastEventId = Math.max(lastEventId, event.eventId);\n }\n applyIncomingEvent(universe, event, { clientId, localState, versions });\n };\n\n const sse = opts.sseUrl && opts.syncUrl\n ? startSSE({\n universe,\n url: opts.sseUrl,\n syncUrl: opts.syncUrl,\n optimistic: opts.optimistic ?? true,\n clientId,\n versions,\n getLastEventId: () => lastEventId,\n onOpen: () => { sseReady = true; },\n onClose: () => { sseReady = false; },\n })\n : null;\n\n const bc = opts.channelName\n ? startBroadcastChannel({\n channelName: opts.channelName,\n clientId,\n onEvent: apply,\n getSnapshot: () => {\n const snap = universe.snapshot();\n const out: Record<string, { state: unknown; version: number }> = {};\n for (const [storeKey, state] of Object.entries(snap)) {\n out[storeKey] = { state, version: versions.get(storeKey) ?? 0 };\n }\n return out;\n },\n onSnapshot: (snap) => {\n for (const [storeKey, rec] of Object.entries(snap)) {\n apply({ storeKey, state: rec.state, version: rec.version });\n }\n },\n })\n : null;\n\n setTimeout(() => bc?.hello(), 0);\n\n const dispatch = async (event: ZunoStateEvent) => {\n if (sse && sseReady) {\n return await sse.dispatch({\n ...event,\n origin: clientId,\n baseVersion: versions.get(event.storeKey) ?? 0,\n });\n }\n\n const nextVersion = (versions.get(event.storeKey) ?? 0) + 1;\n apply({ ...event, version: nextVersion });\n versions.set(event.storeKey, nextVersion);\n\n if (bc) {\n bc.publish({ ...event, version: nextVersion, origin: clientId });\n }\n\n return { ok: true, status: 200, json: null };\n };\n\n const store = <T,>(storeKey: string, init: () => T): BoundStore<T> => {\n const rawStore = universe.getStore<T>(storeKey, init);\n return {\n key: storeKey,\n raw: () => rawStore,\n get: () => rawStore.get(),\n subscribe: (cb) => rawStore.subscribe(cb),\n set: (next) => {\n const prev = rawStore.get();\n const state = typeof next === \"function\" ? (next as (prev: T) => T)(prev) : next;\n return dispatch({ storeKey, state });\n },\n };\n };\n\n return {\n universe,\n clientId,\n store,\n getStore: universe.getStore.bind(universe),\n get: <T,>(key: string, init?: () => T) => universe.getStore<T>(key, init ?? (() => undefined as any)).get(),\n set: async <T,>(key: string, next: T | ((prev: T) => T), init?: () => T) => {\n const s = universe.getStore<T>(key, init ?? (() => undefined as any));\n const state = typeof next === \"function\" ? (next as (prev: T) => T)(s.get()) : next;\n return dispatch({ storeKey: key, state });\n },\n subscribe: <T,>(key: string, init: () => T, cb: (state: T) => void) => universe.getStore<T>(key, init).subscribe(cb),\n dispatch,\n stop: () => {\n sse?.unsubscribe?.();\n bc?.stop?.();\n },\n hydrateSnapshot,\n getLastEventId: () => lastEventId,\n setLastEventId: (id: number) => { lastEventId = id; },\n };\n};\n","/** Universal UI adapter contract */\nexport type ZunoReadable<T> = {\n /** Read current value (sync) */\n getSnapshot(): T;\n\n /**\n * Subscribe to changes.\n * Call `onChange()` whenever snapshot may have changed.\n * Return unsubscribe.\n */\n subscribe(onChange: () => void): () => void;\n\n /** Optional: React SSR (server snapshot) */\n getServerSnapshot?: () => T;\n};\n\n/** Minimal store shape that can be adapted into a readable */\nexport type ZunoSubscribableStore<T> = {\n get(): T;\n subscribe(cb: (state: T) => void): () => void;\n};\n\n/** Adapter helper: convert store => readable */\nexport function toReadable<T>(store: ZunoSubscribableStore<T>): ZunoReadable<T> {\n return {\n getSnapshot: () => store.get(),\n subscribe: (onChange) => store.subscribe(() => onChange()),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/sync/index.ts","../src/core/index.ts","../src/shared/readable.ts"],"names":["applyIncomingEvent","universe","event","context","clientId","versions","current","startSSE","opts","url","syncUrl","getLastEventId","onEvent","resolveConflict","es","retryCount","applyState","queue","isFlushing","flushQueue","keyIndex","reducedQueue","idx","prev","res","data","serverState","serverVersion","nextState","localState","json","err","connect","lastId","connectUrl","e","snap","key","rec","r","delay","dispatchFn","currentV","state","version","startBroadcastChannel","channelName","getSnapshot","onSnapshot","channel","msg","createStore","initial","listeners","next","value","l","listener","createUniverse","stores","init","out","store","existing","snapshot","plain","k","createZuno","lastEventId","hydrateSnapshot","apply","sse","dispatch","bc","storeKey","coreDispatch","v","nextVersion","middlewareAPI","middleware","rawStore","cb","s","id","toReadable","onChange"],"mappings":"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","file":"index.js","sourcesContent":["import type { Universe } from \"../core\";\n\n// --- Types ---\n\nexport type ConflictResolver<T = unknown> = (\n\tlocalState: T,\n\tserverState: T,\n\tstoreKey: string,\n) => T;\n\n/**\n * Authoritative state event.\n */\nexport type ZunoStateEvent = {\n\tstoreKey: string;\n\tstate: unknown; // Was 'any', now strictly 'unknown'\n\tversion?: number;\n\tbaseVersion?: number;\n\torigin?: string;\n\tts?: number;\n\teventId?: number;\n};\n\n/**\n * Generic transport status.\n */\nexport type TransportStatus = {\n\tok: boolean;\n\tstatus: number;\n\tjson: any;\n\treason?: string;\n};\n\n/**\n * Client transport interface.\n */\nexport interface ZunoTransport {\n\tdispatch(event: ZunoStateEvent): Promise<TransportStatus>;\n\tunsubscribe?(): void;\n}\n\n/**\n * Apply incoming event to the universe and local bookkeeping.\n */\nexport function applyIncomingEvent(\n\tuniverse: Universe,\n\tevent: ZunoStateEvent,\n\tcontext: {\n\t\tclientId: string;\n\t\tlocalState: Map<string, unknown>;\n\t\tversions: Map<string, number>;\n\t},\n) {\n\tconst { clientId, versions } = context;\n\n\t// 1. Loopback suppression\n\tif (event.origin === clientId) return;\n\n\t// 2. Version check (if provided by transport)\n\tif (typeof event.version === \"number\") {\n\t\tconst current = versions.get(event.storeKey) ?? 0;\n\t\tif (event.origin !== \"conflict-resolution\" && event.version <= current)\n\t\t\treturn; // Stale check\n\n\t\tversions.set(event.storeKey, event.version);\n\t}\n\n\t// 3. Apply to universe\n\tuniverse.getStore(event.storeKey, () => event.state).set(event.state);\n}\n\n// --- SSE Client ---\n\nexport type SSEOptions = {\n\tuniverse: Universe;\n\turl: string;\n\tsyncUrl: string;\n\toptimistic: boolean;\n\tclientId: string;\n\tversions: Map<string, number>;\n\tgetLastEventId: () => number;\n\tonOpen?: () => void;\n\tonClose?: () => void;\n\tonEvent?: (event: ZunoStateEvent) => void;\n\tresolveConflict?: ConflictResolver;\n};\n\nexport function startSSE(opts: SSEOptions): ZunoTransport {\n\tconst {\n\t\turl,\n\t\tsyncUrl,\n\t\tuniverse,\n\t\tclientId,\n\t\tversions,\n\t\tgetLastEventId,\n\t\tonEvent,\n\t\tresolveConflict,\n\t} = opts;\n\tlet es: EventSource | null = null;\n\tlet retryCount = 0;\n\n\t// Helper to apply state changes\n\tconst applyState = (event: ZunoStateEvent) => {\n\t\tif (onEvent) {\n\t\t\tonEvent(event);\n\t\t} else {\n\t\t\t// Fallback default implementation\n\t\t\tif (typeof event.version === \"number\") {\n\t\t\t\tconst current = versions.get(event.storeKey) ?? 0;\n\t\t\t\tif (event.version <= current) return;\n\t\t\t\tversions.set(event.storeKey, event.version);\n\t\t\t}\n\t\t\tuniverse.getStore(event.storeKey, () => event.state).set(event.state);\n\t\t}\n\t};\n\n\t// --- Offline Support ---\n\tconst queue: ZunoStateEvent[] = [];\n\tlet isFlushing = false;\n\n\tasync function flushQueue() {\n\t\tif (isFlushing || queue.length === 0) return;\n\t\tif (typeof navigator !== \"undefined\" && !navigator.onLine) return;\n\n\t\tisFlushing = true;\n\n\t\t// --- Coalesce / Deduplicate Logic ---\n\t\t// 1. Map index of first occurrence of each storeKey\n\t\tconst keyIndex = new Map<string, number>();\n\t\t// 2. Reduced queue construction\n\t\tconst reducedQueue: ZunoStateEvent[] = [];\n\n\t\tfor (const event of queue) {\n\t\t\tif (keyIndex.has(event.storeKey)) {\n\t\t\t\t// We've seen this key before. We want to update the existing entry in reducedQueue.\n\t\t\t\tconst idx = keyIndex.get(event.storeKey)!;\n\t\t\t\tconst prev = reducedQueue[idx];\n\t\t\t\t// Merge: keep original baseVersion (from the start of the chain) but use NEW state.\n\t\t\t\t// Note: We also likely want to keep the original 'ts' if strictly ordering,\n\t\t\t\t// but state is what matters.\n\t\t\t\t// The 'version' in the event is the *optimistic* version.\n\t\t\t\t// We can keep the *latest* optimistic version (e.g. v10) in the event,\n\t\t\t\t// but server will likely only verify baseVersion.\n\t\t\t\treducedQueue[idx] = { ...event, baseVersion: prev.baseVersion };\n\t\t\t} else {\n\t\t\t\tkeyIndex.set(event.storeKey, reducedQueue.length);\n\t\t\t\treducedQueue.push(event);\n\t\t\t}\n\t\t}\n\n\t\t// Replace original queue with reduced one.\n\t\t// We modify 'queue' in place or reset it.\n\t\t// Since 'queue' is const binding to array, we can't reassign variable,\n\t\t// but we can clear and push.\n\t\tqueue.length = 0;\n\t\tqueue.push(...reducedQueue);\n\n\t\ttry {\n\t\t\twhile (queue.length > 0) {\n\t\t\t\tconst event = queue[0];\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(syncUrl, {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\tbody: JSON.stringify(event),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!res.ok && res.status !== 409) {\n\t\t\t\t\t\t// Keep in queue for retry if it's a transient server error?\n\t\t\t\t\t\t// For now we dequeue on non-network errors to avoid blocking.\n\t\t\t\t\t\tif (res.status >= 400 && res.status < 500) {\n\t\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// For 500, we might want to retry? Let's treat it as network-ish for now.\n\t\t\t\t\t\t// But to be safe and not block forever:\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (res.status === 409) {\n\t\t\t\t\t\tconst data = await res.json();\n\t\t\t\t\t\tif (data.current) {\n\t\t\t\t\t\t\tconst { state: serverState, version: serverVersion } =\n\t\t\t\t\t\t\t\tdata.current;\n\n\t\t\t\t\t\t\tlet nextState = serverState;\n\t\t\t\t\t\t\tif (resolveConflict) {\n\t\t\t\t\t\t\t\tconst localState = universe\n\t\t\t\t\t\t\t\t\t.getStore(event.storeKey, () => null)\n\t\t\t\t\t\t\t\t\t.get();\n\t\t\t\t\t\t\t\tnextState = resolveConflict(\n\t\t\t\t\t\t\t\t\tlocalState,\n\t\t\t\t\t\t\t\t\tserverState,\n\t\t\t\t\t\t\t\t\tevent.storeKey,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// versions.set(event.storeKey, serverVersion); // REMOVE THIS\n\n\t\t\t\t\t\t\t// 2. Apply resolved state locally\n\t\t\t\t\t\t\tapplyState({\n\t\t\t\t\t\t\t\tstoreKey: event.storeKey,\n\t\t\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\t\t\tversion: serverVersion,\n\t\t\t\t\t\t\t\torigin: \"conflict-resolution\",\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// 3. If resolved state differs from server, auto-sync back\n\t\t\t\t\t\t\tif (JSON.stringify(nextState) !== JSON.stringify(serverState)) {\n\t\t\t\t\t\t\t\tqueue.unshift({\n\t\t\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\t\t\t\tbaseVersion: serverVersion,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t} else if (res.ok) {\n\t\t\t\t\t\tconst json = await res.json();\n\t\t\t\t\t\tif (json.event && typeof json.event.version === \"number\") {\n\t\t\t\t\t\t\t// Just update version map\n\t\t\t\t\t\t\tversions.set(event.storeKey, json.event.version);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tqueue.shift();\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(\"[Zuno] Flush failed, retrying later\", err);\n\t\t\t\t\tbreak; // Network error, stop flushing\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tisFlushing = false;\n\t\t}\n\t}\n\tfunction connect() {\n\t\tconst lastId = getLastEventId();\n\t\tconst connectUrl = new URL(url, globalThis.location?.href);\n\t\tif (lastId > 0) connectUrl.searchParams.set(\"lastEventId\", String(lastId));\n\n\t\tes = new EventSource(connectUrl.toString());\n\n\t\tes.addEventListener(\"snapshot\", (e: any) => {\n\t\t\ttry {\n\t\t\t\tconst snap = JSON.parse(e.data);\n\t\t\t\tfor (const [key, rec] of Object.entries(snap)) {\n\t\t\t\t\tconst r = rec as { state: any; version: number };\n\t\t\t\t\tapplyState({\n\t\t\t\t\t\tstoreKey: key,\n\t\t\t\t\t\tstate: r.state,\n\t\t\t\t\t\tversion: r.version,\n\t\t\t\t\t\torigin: \"server\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[Zuno] Failed to parse snapshot\", err);\n\t\t\t}\n\t\t});\n\n\t\tes.addEventListener(\"state\", (e: any) => {\n\t\t\ttry {\n\t\t\t\tconst event = JSON.parse(e.data) as ZunoStateEvent;\n\t\t\t\t// Loopback suppression logic is now handled by caller (middleware/core) if onEvent is provided,\n\t\t\t\t// OR we should keep it here?\n\t\t\t\t// If we dispatch to middleware, the middleware might log it.\n\t\t\t\t// But if it's our own event, we shouldn't re-apply it if we already did optimally?\n\t\t\t\t// Actually, startSSE optimistic logic does it.\n\t\t\t\t// If we receive an echo, we might want to update version but not state if it matches?\n\t\t\t\t// Let's pass it to applyState.\n\n\t\t\t\tif (event.origin === clientId) return;\n\n\t\t\t\tapplyState(event);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[Zuno] Failed to parse SSE event\", err);\n\t\t\t}\n\t\t});\n\n\t\tes.onopen = () => {\n\t\t\tretryCount = 0;\n\t\t\topts.onOpen?.();\n\t\t\tflushQueue();\n\t\t};\n\n\t\tes.onerror = () => {\n\t\t\tes?.close();\n\t\t\topts.onClose?.();\n\t\t\tconst delay = Math.min(1000 * 2 ** retryCount, 30000);\n\t\t\tretryCount++;\n\t\t\tsetTimeout(connect, delay);\n\t\t};\n\t}\n\n\tif (typeof window !== \"undefined\") {\n\t\twindow.addEventListener(\"online\", flushQueue);\n\t}\n\n\tconnect();\n\n\tconst dispatchFn = async (\n\t\tevent: ZunoStateEvent,\n\t): Promise<TransportStatus> => {\n\t\ttry {\n\t\t\tif (opts.optimistic) {\n\t\t\t\t// Manually apply optimistic update (local origin)\n\t\t\t\t// We can call applyState here OR let middleware do it if we pass it through middleware?\n\t\t\t\t// If we pass through middleware, the middleware calls dispatch.\n\t\t\t\t// This IS the dispatch.\n\t\t\t\t// So we should apply it here.\n\t\t\t\t// BUT if we want middleware to run on *incoming*, we are separating the two flows.\n\t\t\t\t// For outgoing, we apply locally.\n\t\t\t\t// Note: if user logs, they see the action.\n\t\t\t\t// We don't need to call applyState for outgoing if startSSE is responsible for transport only?\n\t\t\t\t// BUT startSSE manages local versions map.\n\n\t\t\t\tuniverse.getStore(event.storeKey, () => event.state).set(event.state);\n\t\t\t}\n\n\t\t\t// Check online status first\n\t\t\tif (typeof navigator !== \"undefined\" && !navigator.onLine) {\n\t\t\t\tqueue.push(event);\n\t\t\t\t// Optimistically increment version so next event uses correct baseVersion\n\t\t\t\tconst currentV = versions.get(event.storeKey) ?? 0;\n\t\t\t\tversions.set(event.storeKey, currentV + 1);\n\t\t\t\treturn { ok: false, status: 0, json: null, reason: \"OFFLINE_QUEUED\" };\n\t\t\t}\n\n\t\t\tconst res = await fetch(syncUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify(event),\n\t\t\t});\n\n\t\t\tif (res.status === 409) {\n\t\t\t\tconst data = await res.json();\n\t\t\t\tif (data.current) {\n\t\t\t\t\tconst { state: serverState, version: serverVersion } = data.current;\n\n\t\t\t\t\tlet nextState = serverState;\n\t\t\t\t\tif (resolveConflict) {\n\t\t\t\t\t\tconst localState = universe\n\t\t\t\t\t\t\t.getStore(event.storeKey, () => null)\n\t\t\t\t\t\t\t.get();\n\t\t\t\t\t\tnextState = resolveConflict(\n\t\t\t\t\t\t\tlocalState,\n\t\t\t\t\t\t\tserverState,\n\t\t\t\t\t\t\tevent.storeKey,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// versions.set(event.storeKey, serverVersion); // REMOVE THIS: applyState handles it\n\t\t\t\t\tapplyState({\n\t\t\t\t\t\tstoreKey: event.storeKey,\n\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\tversion: serverVersion,\n\t\t\t\t\t\torigin: \"conflict-resolution\",\n\t\t\t\t\t});\n\n\t\t\t\t\tif (JSON.stringify(nextState) !== JSON.stringify(serverState)) {\n\t\t\t\t\t\t// Return special status to indicate a merge happened and we should retry?\n\t\t\t\t\t\t// Or just fire new fetch?\n\t\t\t\t\t\t// Let's fire new fetch recursively by calling dispatch (but careful about infinite loop if 409 happens again).\n\t\t\t\t\t\t// Or easier:\n\n\t\t\t\t\t\treturn await dispatchFn({\n\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\tstate: nextState,\n\t\t\t\t\t\t\tbaseVersion: serverVersion,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { ok: false, status: 409, json: data, reason: \"CONFLICT\" };\n\t\t\t}\n\n\t\t\tif (!res.ok)\n\t\t\t\treturn { ok: false, status: res.status, json: await res.json() };\n\n\t\t\tconst json = await res.json();\n\t\t\tif (json.event) {\n\t\t\t\tconst { state, version } = json.event;\n\t\t\t\tif (typeof version === \"number\") {\n\t\t\t\t\tversions.set(event.storeKey, version);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { ok: true, status: 200, json };\n\t\t} catch (err) {\n\t\t\t// Network failure catch\n\t\t\tconsole.warn(\"[Zuno] Dispatch failed, queuing\", err);\n\t\t\tqueue.push(event);\n\t\t\t// Optimistically increment version here too, assuming the previous one is \"pending\"\n\t\t\t// and subsequent edits should build on top of it.\n\t\t\tconst currentV = versions.get(event.storeKey) ?? 0;\n\t\t\tversions.set(event.storeKey, currentV + 1);\n\n\t\t\tsetTimeout(flushQueue, 1000);\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\tstatus: 500,\n\t\t\t\tjson: err,\n\t\t\t\treason: \"NETWORK_ERROR_QUEUED\",\n\t\t\t};\n\t\t}\n\t};\n\n\treturn {\n\t\tdispatch: dispatchFn,\n\t\tunsubscribe: () => {\n\t\t\tes?.close();\n\t\t\tif (typeof window !== \"undefined\") {\n\t\t\t\twindow.removeEventListener(\"online\", flushQueue);\n\t\t\t}\n\t\t},\n\t};\n}\n\n// --- BroadcastChannel ---\n\nexport type BCOptions = {\n\tchannelName: string;\n\tclientId: string;\n\tonEvent: (event: ZunoStateEvent) => void;\n\tgetSnapshot: () => Record<string, { state: unknown; version: number }>;\n\tonSnapshot: (\n\t\tsnap: Record<string, { state: unknown; version: number }>,\n\t) => void;\n};\n\nexport function startBroadcastChannel(opts: BCOptions) {\n\tconst { channelName, clientId, onEvent, getSnapshot, onSnapshot } = opts;\n\tconst channel = new BroadcastChannel(channelName);\n\n\tchannel.onmessage = (e) => {\n\t\tconst msg = e.data;\n\t\tif (msg.origin === clientId) return;\n\n\t\tif (msg.type === \"event\") onEvent(msg.event);\n\t\tif (msg.type === \"hello\")\n\t\t\tchannel.postMessage({\n\t\t\t\ttype: \"snapshot\",\n\t\t\t\tsnapshot: getSnapshot(),\n\t\t\t\torigin: clientId,\n\t\t\t});\n\t\tif (msg.type === \"snapshot\") onSnapshot(msg.snapshot);\n\t};\n\n\treturn {\n\t\tpublish: (event: ZunoStateEvent) =>\n\t\t\tchannel.postMessage({ type: \"event\", event, origin: clientId }),\n\t\thello: () => channel.postMessage({ type: \"hello\", origin: clientId }),\n\t\tstop: () => channel.close(),\n\t};\n}\n","import type {\n\tConflictResolver,\n\tTransportStatus,\n\tZunoStateEvent,\n} from \"../sync\";\nimport { applyIncomingEvent, startBroadcastChannel, startSSE } from \"../sync\";\n\n// --- Types ---\n\n/**\n * Authoritative snapshot of the entire universe.\n */\nexport type ZunoSnapshot = {\n\tstate: Record<string, { state: unknown; version: number }>;\n\tlastEventId: number;\n};\n\n/**\n * A simple state container for a single keyed value.\n */\nexport interface Store<T> {\n\tget(): T;\n\tset(next: T | ((prev: T) => T)): void;\n\tsubscribe(listener: (state: T) => void): () => void;\n}\n\n/**\n * A Universe manages many stores.\n */\nexport interface Universe {\n\tgetStore<T>(key: string, init: () => T): Store<T>;\n\tsnapshot(): Record<string, unknown>;\n\trestore(data: Record<string, unknown>): void;\n\tdelete(key: string): void;\n\tclear(): void;\n\thydrateSnapshot(snapshot: ZunoSnapshot): void;\n}\n\n// --- Middleware Types ---\n\nexport type Dispatch = (event: ZunoStateEvent) => Promise<TransportStatus>;\n\nexport type MiddlewareAPI = {\n\tuniverse: Universe;\n\tclientId: string;\n\tversions: Map<string, number>;\n};\n\nexport type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch;\n\n/**\n * Options for creating a Zuno instance.\n */\nexport type CreateZunoOptions = {\n\t/** Optional pre-existing universe. */\n\tuniverse?: Universe;\n\t/** BroadcastChannel name for local tab sync. */\n\tchannelName?: string;\n\t/** SSE endpoint URL. */\n\tsseUrl?: string;\n\t/** Sync endpoint URL (required if sseUrl is provided). */\n\tsyncUrl?: string;\n\t/** Apply updates locally before server confirmation (default: true). */\n\toptimistic?: boolean;\n\t/** Unique client identifier (default: random UUID). */\n\tclientId?: string;\n\t/** Middleware chain. */\n\tmiddleware?: Middleware[];\n\t/** Optional function to resolve 409 conflicts. */\n\tresolveConflict?: ConflictResolver;\n};\n\n/**\n * An extended interface for a Zuno store that includes methods for setting state\n * and a unique key identifier. This represents a store that has been \"bound\" or registered.\n */\nexport type BoundStore<T> = {\n\tkey: string;\n\tget: () => T;\n\tset: (next: T | ((prev: T) => T)) => Promise<any>;\n\tsubscribe: (cb: (state: T) => void) => () => void;\n\traw: () => Store<T>;\n};\n\n// --- Store Implementation ---\n\n/**\n * Creates a raw ZUNO state management store.\n */\nexport const createStore = <T>(initial: T): Store<T> => {\n\tlet state = initial;\n\tconst listeners = new Set<(state: T) => void>();\n\n\treturn {\n\t\tget: () => state,\n\t\tset: (next) => {\n\t\t\tconst value =\n\t\t\t\ttypeof next === \"function\" ? (next as (prev: T) => T)(state) : next;\n\t\t\tif (Object.is(value, state)) return;\n\t\t\tstate = value;\n\t\t\tlisteners.forEach((l) => {\n\t\t\t\tl(state);\n\t\t\t});\n\t\t},\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => listeners.delete(listener);\n\t\t},\n\t};\n};\n\n// --- Universe Implementation ---\n\n/**\n * Creates a ZUNO Universe to manage multiple stores.\n */\nexport const createUniverse = (): Universe => {\n\tconst stores = new Map<string, Store<any>>();\n\n\tconst universe: Universe = {\n\t\tgetStore<T>(key: string, init: () => T): Store<T> {\n\t\t\tif (!stores.has(key)) {\n\t\t\t\tstores.set(key, createStore(init()));\n\t\t\t}\n\t\t\treturn stores.get(key)! as Store<T>;\n\t\t},\n\t\tsnapshot(): Record<string, unknown> {\n\t\t\tconst out: Record<string, unknown> = {};\n\t\t\tfor (const [key, store] of stores.entries()) {\n\t\t\t\tout[key] = store.get();\n\t\t\t}\n\t\t\treturn out;\n\t\t},\n\t\trestore(data: Record<string, unknown>): void {\n\t\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\t\tconst existing = stores.get(key);\n\t\t\t\tif (existing) {\n\t\t\t\t\texisting.set(value as any);\n\t\t\t\t} else {\n\t\t\t\t\tstores.set(key, createStore(value as any));\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdelete(key: string): void {\n\t\t\tstores.delete(key);\n\t\t},\n\t\tclear(): void {\n\t\t\tstores.clear();\n\t\t},\n\t\thydrateSnapshot(snapshot: ZunoSnapshot) {\n\t\t\tconst plain: Record<string, unknown> = {};\n\t\t\tfor (const [k, rec] of Object.entries(snapshot.state)) {\n\t\t\t\tplain[k] = rec.state;\n\t\t\t}\n\t\t\tthis.restore(plain);\n\t\t},\n\t};\n\n\treturn universe;\n};\n\n// --- Main Zuno Factory ---\n\n/**\n * Creates a Zuno instance for distributed state synchronization.\n */\nexport const createZuno = (opts: CreateZunoOptions = {}) => {\n\tconst localState = new Map<string, unknown>();\n\tconst versions = new Map<string, number>();\n\tconst universe = opts.universe ?? createUniverse();\n\tconst clientId =\n\t\topts.clientId ?? globalThis.crypto?.randomUUID?.() ?? String(Math.random());\n\tlet sseReady = false;\n\tlet lastEventId = 0;\n\n\tfunction hydrateSnapshot(snapshot: ZunoSnapshot) {\n\t\tconst plain: Record<string, unknown> = {};\n\t\tfor (const [k, rec] of Object.entries(snapshot.state)) {\n\t\t\tplain[k] = rec.state;\n\t\t\tversions.set(k, rec.version);\n\t\t}\n\t\tuniverse.restore(plain);\n\t\tlastEventId = snapshot.lastEventId;\n\t}\n\n\tconst apply = (event: ZunoStateEvent) => {\n\t\tif (typeof event.eventId === \"number\") {\n\t\t\tlastEventId = Math.max(lastEventId, event.eventId);\n\t\t}\n\t\tapplyIncomingEvent(universe, event, { clientId, localState, versions });\n\t};\n\n\tconst sse =\n\t\topts.sseUrl && opts.syncUrl\n\t\t\t? startSSE({\n\t\t\t\t\tuniverse,\n\t\t\t\t\turl: opts.sseUrl,\n\t\t\t\t\tsyncUrl: opts.syncUrl,\n\t\t\t\t\toptimistic: opts.optimistic ?? true,\n\t\t\t\t\tclientId,\n\t\t\t\t\tversions,\n\t\t\t\t\tgetLastEventId: () => lastEventId,\n\t\t\t\t\tonOpen: () => {\n\t\t\t\t\t\tsseReady = true;\n\t\t\t\t\t},\n\t\t\t\t\tonClose: () => {\n\t\t\t\t\t\tsseReady = false;\n\t\t\t\t\t},\n\t\t\t\t\tonEvent: (e) => dispatch(e), // Route incoming SSE events through middleware\n\t\t\t\t\tresolveConflict: opts.resolveConflict,\n\t\t\t\t})\n\t\t\t: null;\n\n\tconst bc = opts.channelName\n\t\t? startBroadcastChannel({\n\t\t\t\tchannelName: opts.channelName,\n\t\t\t\tclientId,\n\t\t\t\tonEvent: (e) => dispatch(e), // Route incoming BC events through middleware\n\t\t\t\tgetSnapshot: () => {\n\t\t\t\t\tconst snap = universe.snapshot();\n\t\t\t\t\tconst out: Record<string, { state: unknown; version: number }> = {};\n\t\t\t\t\tfor (const [storeKey, state] of Object.entries(snap)) {\n\t\t\t\t\t\tout[storeKey] = { state, version: versions.get(storeKey) ?? 0 };\n\t\t\t\t\t}\n\t\t\t\t\treturn out;\n\t\t\t\t},\n\t\t\t\tonSnapshot: (snap) => {\n\t\t\t\t\tfor (const [storeKey, rec] of Object.entries(snap)) {\n\t\t\t\t\t\tapply({ storeKey, state: rec.state, version: rec.version });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t})\n\t\t: null;\n\n\tsetTimeout(() => bc?.hello(), 0);\n\n\tconst coreDispatch = async (\n\t\tevent: ZunoStateEvent,\n\t): Promise<TransportStatus> => {\n\t\t// 1. Incoming Event (from Server or other Tab via BC)\n\t\tif (event.origin && event.origin !== clientId) {\n\t\t\tapply(event);\n\t\t\t// Incoming events don't need to be re-broadcasted to network or BC typically,\n\t\t\t// unless acting as a relay (not current design).\n\t\t\treturn { ok: true, status: 200, json: null };\n\t\t}\n\n\t\t// 2. Outgoing Event (Local Action)\n\t\tif (sse) {\n\t\t\tconst res = await sse.dispatch({\n\t\t\t\t...event,\n\t\t\t\torigin: clientId,\n\t\t\t\tbaseVersion: versions.get(event.storeKey) ?? 0,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\t(res.reason === \"OFFLINE_QUEUED\" ||\n\t\t\t\t\tres.reason === \"NETWORK_ERROR_QUEUED\") &&\n\t\t\t\tbc\n\t\t\t) {\n\t\t\t\tconst v = versions.get(event.storeKey) ?? 0;\n\t\t\t\tbc.publish({ ...event, version: v, origin: clientId });\n\t\t\t}\n\n\t\t\treturn res;\n\t\t}\n\n\t\tconst nextVersion = (versions.get(event.storeKey) ?? 0) + 1;\n\t\tapply({ ...event, version: nextVersion });\n\t\tversions.set(event.storeKey, nextVersion);\n\n\t\tif (bc) {\n\t\t\tbc.publish({ ...event, version: nextVersion, origin: clientId });\n\t\t}\n\n\t\treturn { ok: true, status: 200, json: null };\n\t};\n\n\t// --- Middleware Composition ---\n\tlet dispatch: Dispatch = coreDispatch;\n\n\tif (opts.middleware && opts.middleware.length > 0) {\n\t\tconst middlewareAPI: MiddlewareAPI = {\n\t\t\tuniverse,\n\t\t\tclientId,\n\t\t\tversions,\n\t\t};\n\t\tconst chain = opts.middleware.map((middleware) =>\n\t\t\tmiddleware(middlewareAPI),\n\t\t);\n\t\tdispatch = chain.reduceRight(\n\t\t\t(next, middleware) => middleware(next),\n\t\t\tcoreDispatch,\n\t\t);\n\t}\n\n\tconst store = <T>(storeKey: string, init: () => T): BoundStore<T> => {\n\t\tconst rawStore = universe.getStore<T>(storeKey, init);\n\t\treturn {\n\t\t\tkey: storeKey,\n\t\t\traw: () => rawStore,\n\t\t\tget: () => rawStore.get(),\n\t\t\tsubscribe: (cb) => rawStore.subscribe(cb),\n\t\t\tset: (next) => {\n\t\t\t\tconst prev = rawStore.get();\n\t\t\t\tconst state =\n\t\t\t\t\ttypeof next === \"function\" ? (next as (prev: T) => T)(prev) : next;\n\t\t\t\treturn dispatch({ storeKey, state });\n\t\t\t},\n\t\t};\n\t};\n\n\treturn {\n\t\tuniverse,\n\t\tclientId,\n\t\tstore,\n\t\tgetStore: universe.getStore.bind(universe),\n\t\tget: <T>(key: string, init?: () => T) =>\n\t\t\tuniverse.getStore<T>(key, init ?? (() => undefined as any)).get(),\n\t\tset: async <T>(key: string, next: T | ((prev: T) => T), init?: () => T) => {\n\t\t\tconst s = universe.getStore<T>(key, init ?? (() => undefined as any));\n\t\t\tconst state =\n\t\t\t\ttypeof next === \"function\" ? (next as (prev: T) => T)(s.get()) : next;\n\t\t\treturn dispatch({ storeKey: key, state });\n\t\t},\n\t\tsubscribe: <T>(key: string, init: () => T, cb: (state: T) => void) =>\n\t\t\tuniverse.getStore<T>(key, init).subscribe(cb),\n\t\tdispatch,\n\t\tstop: () => {\n\t\t\tsse?.unsubscribe?.();\n\t\t\tbc?.stop?.();\n\t\t},\n\t\thydrateSnapshot,\n\t\tgetLastEventId: () => lastEventId,\n\t\tsetLastEventId: (id: number) => {\n\t\t\tlastEventId = id;\n\t\t},\n\t};\n};\n","/** Universal UI adapter contract */\nexport type ZunoReadable<T> = {\n\t/** Read current value (sync) */\n\tgetSnapshot(): T;\n\n\t/**\n\t * Subscribe to changes.\n\t * Call `onChange()` whenever snapshot may have changed.\n\t * Return unsubscribe.\n\t */\n\tsubscribe(onChange: () => void): () => void;\n\n\t/** Optional: React SSR (server snapshot) */\n\tgetServerSnapshot?: () => T;\n};\n\n/** Minimal store shape that can be adapted into a readable */\nexport type ZunoSubscribableStore<T> = {\n\tget(): T;\n\tsubscribe(cb: (state: T) => void): () => void;\n};\n\n/** Adapter helper: convert store => readable */\nexport function toReadable<T>(\n\tstore: ZunoSubscribableStore<T>,\n): ZunoReadable<T> {\n\treturn {\n\t\tgetSnapshot: () => store.get(),\n\t\tsubscribe: (onChange) => store.subscribe(() => onChange()),\n\t};\n}\n"]}
|
package/dist/server/index.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
'use strict';var p=new Map,
|
|
1
|
+
'use strict';var p=new Map,d=t=>p.get(t),f=t=>{let e=p.get(t.storeKey)??{version:0},o=typeof t.version=="number"?t.version:e.version+1;p.set(t.storeKey,{state:t.state,version:o});},c=()=>Object.fromEntries(p),h=1e3,w=1,a=[],S=t=>(t.eventId=w++,a.push(t),a.length>h&&a.shift(),t),g=t=>a.filter(e=>(e?.eventId??0)>t),l=()=>a[a.length-1]?.eventId??0,v=new Set,y=t=>(v.add(t),()=>{v.delete(t);}),E=t=>{v.forEach(e=>{e(t);});};function m(t){let e=d(t.storeKey)??{state:void 0,version:0};if(typeof t.baseVersion=="number"&&t.baseVersion!==e.version)return {ok:false,reason:"VERSION_CONFLICT",current:e};let o=e.version+1,n={...t,version:o};return f(n),S(n),E(n),{ok:true,event:n}}function C(t,e){let o={state:c(),lastEventId:l()};"json"in e&&typeof e.json=="function"?e.json(o):(e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(o)));}var A=(t,e,o)=>{e.writeHead(200,{"Cache-Control":"no-cache, no-transform","Content-Type":"text/event-stream; charset=utf-8",Connection:"keep-alive","X-Accel-Buffering":"no",...o}),e.flushHeaders?.();let n=t.headers["last-event-id"]||new URL(t.url||"","http://localhost").searchParams.get("lastEventId"),r=Number.parseInt(Array.isArray(n)?n[0]:n??"0",10)||0;if(r>0){let i=g(r);for(let u of i)e.write(`id: ${u.eventId}
|
|
2
2
|
`),e.write(`event: state
|
|
3
3
|
`),e.write(`data: ${JSON.stringify(u)}
|
|
4
4
|
|
|
5
5
|
`);}else e.write(`event: snapshot
|
|
6
6
|
`),e.write(`data: ${JSON.stringify(c())}
|
|
7
7
|
|
|
8
|
-
`);let s=
|
|
8
|
+
`);let s=y(i=>{e.write(`id: ${i.eventId}
|
|
9
9
|
`),e.write(`event: state
|
|
10
10
|
`),e.write(`data: ${JSON.stringify(i)}
|
|
11
11
|
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
|
|
14
14
|
`);},15e3);e.write(`: connected
|
|
15
15
|
|
|
16
|
-
`),t.on("close",()=>{clearInterval(I),s(),e.end();});},
|
|
16
|
+
`),t.on("close",()=>{clearInterval(I),s(),e.end();});},x=(t,e)=>{let n="";t.on("data",r=>{n+=r.toString("utf8"),n.length>524288&&(e.writeHead(413,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:false,reason:"PAYLOAD_TOO_LARGE"})),t.destroy());}),t.on("end",()=>{try{let r=JSON.parse(n||"{}"),s=m(r);if(!s.ok){s.reason==="VERSION_CONFLICT"&&(e.writeHead(409,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:!1,reason:"VERSION_CONFLICT",current:s.current})));return}e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:!0,event:s.event}));}catch{e.writeHead(400,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:false,reason:"INVALID_JSON"}));}});},L=(t,e)=>x(t,e);exports.appendEvent=S;exports.applyStateEvent=m;exports.createSSEConnection=A;exports.getEventsAfter=g;exports.getLastEventId=l;exports.getUniverseRecord=d;exports.getUniverseState=c;exports.publishToStateEvent=E;exports.sendSnapshot=C;exports.setUniverseState=L;exports.subscribeToStateEvents=y;exports.syncUniverseState=x;exports.updateUniverseState=f;//# sourceMappingURL=index.cjs.map
|
|
17
17
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/core.ts","../../src/server/apply-state-event.ts","../../src/server/sse-handler.ts","../../src/server/snapshot-handler.ts"],"names":["universeState","getUniverseRecord","storeKey","updateUniverseState","event","current","nextVersion","getUniverseState","MAX_EVENTS","nextEventId","eventLog","appendEvent","getEventsAfter","lastEventId","getLastEventId","listeners","subscribeToStateEvents","listener","publishToStateEvent","applyStateEvent","incoming","createSSEConnection","req","res","headers","raw","missed","unsubscribe","heartbeat","syncUniverseState","body","chunk","result","setUniverseState","sendSnapshot","_req","snapshot"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ZunoStateEvent } from \"../sync\";\n\n// --- Types ---\n\nexport type UniverseRecord = {\n state: any;\n version: number;\n};\n\nexport type ZunoStateListener = (event: ZunoStateEvent) => void;\n\n// --- State Store ---\n\nconst universeState = new Map<string, UniverseRecord>();\n\nexport const getUniverseRecord = (storeKey: string): UniverseRecord | undefined => {\n return universeState.get(storeKey);\n};\n\nexport const updateUniverseState = (event: ZunoStateEvent) => {\n const current = universeState.get(event.storeKey) ?? { state: undefined, version: 0 };\n const nextVersion = typeof event.version === \"number\" ? event.version : current.version + 1;\n universeState.set(event.storeKey, { state: event.state, version: nextVersion });\n};\n\nexport const getUniverseState = () => {\n return Object.fromEntries(universeState);\n};\n\n// --- Event Log ---\n\nconst MAX_EVENTS = 1000;\nlet nextEventId = 1;\nconst eventLog: ZunoStateEvent[] = [];\n\nexport const appendEvent = (event: ZunoStateEvent) => {\n event.eventId = nextEventId++;\n eventLog.push(event);\n if (eventLog.length > MAX_EVENTS) {\n eventLog.shift();\n }\n return event;\n};\n\nexport const getEventsAfter = (lastEventId: number) => {\n return eventLog.filter((event) => (event?.eventId ?? 0) > lastEventId);\n};\n\nexport const getLastEventId = () => {\n return eventLog[eventLog.length - 1]?.eventId ?? 0;\n};\n\n// --- State Bus (Events) ---\n\nconst listeners = new Set<ZunoStateListener>();\n\nexport const subscribeToStateEvents = (listener: ZunoStateListener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n};\n\nexport const publishToStateEvent = (event: ZunoStateEvent) => {\n listeners.forEach((listener) => listener(event));\n};\n","import { getUniverseRecord, updateUniverseState, publishToStateEvent, appendEvent } from \"./core\";\nimport type { ZunoStateEvent } from \"../sync\";\n\nexport type ApplyResult = \n | { ok: true; event: ZunoStateEvent }\n | { ok: false; reason: \"VERSION_CONFLICT\"; current: { state: any; version: number } }\n | { ok: false; reason: string; current?: never };\n\n/**\n * Validates and applies a state event to the server universe.\n */\nexport function applyStateEvent(incoming: ZunoStateEvent): ApplyResult {\n const current = getUniverseRecord(incoming.storeKey) ?? { state: undefined, version: 0 };\n\n // Strict version check\n if (typeof incoming.baseVersion === \"number\" && incoming.baseVersion !== current.version) {\n return { ok: false, reason: \"VERSION_CONFLICT\", current };\n }\n\n // Increment version\n const nextVersion = current.version + 1;\n const event = { ...incoming, version: nextVersion };\n\n // Persistence\n updateUniverseState(event);\n appendEvent(event);\n\n // Notify SSE subscribers\n publishToStateEvent(event);\n\n return { ok: true, event };\n}\n","import { subscribeToStateEvents } from \"./core\";\nimport { getUniverseState } from \"./core\";\nimport { getEventsAfter } from \"./core\";\nimport type { ZunoStateEvent } from \"../sync\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { applyStateEvent } from \"./apply-state-event\";\n\ntype IncomingHeaders = IncomingMessage[\"headers\"];\n\n/**\n * Creates a Server-Sent Events (SSE) connection for Zuno state updates.\n */\nexport const createSSEConnection = (req: IncomingMessage, res: ServerResponse, headers: IncomingHeaders) => {\n res.writeHead(200, {\n \"Cache-Control\": \"no-cache, no-transform\",\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\",\n ...headers\n });\n\n res.flushHeaders?.();\n\n const raw = req.headers[\"last-event-id\"] || new URL(req.url || \"\", \"http://localhost\").searchParams.get(\"lastEventId\");\n const lastEventId = Number.parseInt(Array.isArray(raw) ? raw[0] : (raw ?? \"0\"), 10) || 0;\n\n if (lastEventId > 0) {\n const missed = getEventsAfter(lastEventId);\n for (const event of missed) {\n res.write(`id: ${event.eventId}\\n`);\n res.write(`event: state\\n`);\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n }\n } else {\n res.write(`event: snapshot\\n`);\n res.write(`data: ${JSON.stringify(getUniverseState())}\\n\\n`);\n }\n\n const unsubscribe = subscribeToStateEvents((event: ZunoStateEvent) => {\n res.write(`id: ${event.eventId}\\n`);\n res.write(`event: state\\n`);\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n });\n\n const heartbeat = setInterval(() => {\n res.write(`: ping ${Date.now()}\\n\\n`);\n }, 15000);\n\n res.write(\": connected \\n\\n\");\n\n req.on(\"close\", () => {\n clearInterval(heartbeat);\n unsubscribe();\n res.end();\n });\n};\n\n/**\n * Synchronizes the Zuno universe state by applying an incoming event.\n */\nexport const syncUniverseState = (req: IncomingMessage, res: ServerResponse) => {\n const MAX_BODY_BYTES = 512 * 1024; // 512KB safety\n let body = \"\";\n\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString(\"utf8\");\n if (body.length > MAX_BODY_BYTES) {\n res.writeHead(413, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, reason: \"PAYLOAD_TOO_LARGE\" }));\n req.destroy();\n }\n });\n\n req.on(\"end\", () => {\n try {\n const incoming: ZunoStateEvent = JSON.parse(body || \"{}\") as any;\n const result = applyStateEvent(incoming);\n\n if (!result.ok) {\n if (result.reason === \"VERSION_CONFLICT\") {\n res.writeHead(409, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, reason: \"VERSION_CONFLICT\", current: result.current }));\n }\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true, event: result.event }));\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, reason: \"INVALID_JSON\" }));\n }\n });\n};\n\nexport const setUniverseState = (req: IncomingMessage, res: ServerResponse) => {\n return syncUniverseState(req, res);\n};\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport { getUniverseState, getLastEventId } from \"./core\";\n\n/**\n * Sends a snapshot of the current universe state to the response.\n * Compatible with both Express and raw Node.js http.\n */\nexport function sendSnapshot(_req: IncomingMessage, res: ServerResponse) {\n const snapshot = {\n state: getUniverseState(),\n lastEventId: getLastEventId(),\n };\n\n // Check for Express-like .json() method\n if (\"json\" in res && typeof (res as any).json === \"function\") {\n (res as any).json(snapshot);\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(snapshot));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/core.ts","../../src/server/apply-state-event.ts","../../src/server/snapshot-handler.ts","../../src/server/sse-handler.ts"],"names":["universeState","getUniverseRecord","storeKey","updateUniverseState","event","current","nextVersion","getUniverseState","MAX_EVENTS","nextEventId","eventLog","appendEvent","getEventsAfter","lastEventId","getLastEventId","listeners","subscribeToStateEvents","listener","publishToStateEvent","applyStateEvent","incoming","sendSnapshot","_req","res","snapshot","createSSEConnection","req","headers","raw","missed","unsubscribe","heartbeat","syncUniverseState","body","chunk","result","setUniverseState"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ZunoStateEvent } from \"../sync\";\n\n// --- Types ---\n\nexport type UniverseRecord = {\n\tstate: unknown; // Was 'any'\n\tversion: number;\n};\n\nexport type ZunoStateListener = (event: ZunoStateEvent) => void;\n\n// --- State Store ---\n\nconst universeState = new Map<string, UniverseRecord>();\n\nexport const getUniverseRecord = (\n\tstoreKey: string,\n): UniverseRecord | undefined => {\n\treturn universeState.get(storeKey);\n};\n\nexport const updateUniverseState = (event: ZunoStateEvent) => {\n\tconst current = universeState.get(event.storeKey) ?? {\n\t\tstate: undefined,\n\t\tversion: 0,\n\t};\n\tconst nextVersion =\n\t\ttypeof event.version === \"number\" ? event.version : current.version + 1;\n\tuniverseState.set(event.storeKey, {\n\t\tstate: event.state,\n\t\tversion: nextVersion,\n\t});\n};\n\nexport const getUniverseState = () => {\n\treturn Object.fromEntries(universeState);\n};\n\n// --- Event Log ---\n\nconst MAX_EVENTS = 1000;\nlet nextEventId = 1;\nconst eventLog: ZunoStateEvent[] = [];\n\nexport const appendEvent = (event: ZunoStateEvent) => {\n\tevent.eventId = nextEventId++;\n\teventLog.push(event);\n\tif (eventLog.length > MAX_EVENTS) {\n\t\teventLog.shift();\n\t}\n\treturn event;\n};\n\nexport const getEventsAfter = (lastEventId: number) => {\n\treturn eventLog.filter((event) => (event?.eventId ?? 0) > lastEventId);\n};\n\nexport const getLastEventId = () => {\n\treturn eventLog[eventLog.length - 1]?.eventId ?? 0;\n};\n\n// --- State Bus (Events) ---\n\nconst listeners = new Set<ZunoStateListener>();\n\nexport const subscribeToStateEvents = (listener: ZunoStateListener) => {\n\tlisteners.add(listener);\n\treturn () => {\n\t\tlisteners.delete(listener);\n\t};\n};\n\nexport const publishToStateEvent = (event: ZunoStateEvent) => {\n\tlisteners.forEach((listener) => {\n\t\tlistener(event);\n\t});\n};\n","import type { ZunoStateEvent } from \"../sync\";\nimport {\n\tappendEvent,\n\tgetUniverseRecord,\n\tpublishToStateEvent,\n\tupdateUniverseState,\n} from \"./core\";\n\nexport type ApplyResult =\n\t| { ok: true; event: ZunoStateEvent }\n\t| {\n\t\t\tok: false;\n\t\t\treason: \"VERSION_CONFLICT\";\n\t\t\tcurrent: { state: unknown; version: number };\n\t }\n\t| { ok: false; reason: string; current?: never };\n\n/**\n * Validates and applies a state event to the server universe.\n */\nexport function applyStateEvent(incoming: ZunoStateEvent): ApplyResult {\n\tconst current = getUniverseRecord(incoming.storeKey) ?? {\n\t\tstate: undefined,\n\t\tversion: 0,\n\t};\n\n\t// Strict version check\n\tif (\n\t\ttypeof incoming.baseVersion === \"number\" &&\n\t\tincoming.baseVersion !== current.version\n\t) {\n\t\treturn { ok: false, reason: \"VERSION_CONFLICT\", current };\n\t}\n\n\t// Increment version\n\tconst nextVersion = current.version + 1;\n\tconst event = { ...incoming, version: nextVersion };\n\n\t// Persistence\n\tupdateUniverseState(event);\n\tappendEvent(event);\n\n\t// Notify SSE subscribers\n\tpublishToStateEvent(event);\n\n\treturn { ok: true, event };\n}\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport { getLastEventId, getUniverseState } from \"./core\";\n\n/**\n * Sends a snapshot of the current universe state to the response.\n * Compatible with both Express and raw Node.js http.\n */\nexport function sendSnapshot(_req: IncomingMessage, res: ServerResponse) {\n\tconst snapshot = {\n\t\tstate: getUniverseState(),\n\t\tlastEventId: getLastEventId(),\n\t};\n\n\t// Check for Express-like .json() method\n\t// biome-ignore lint/suspicious/noExplicitAny: Checking for dynamic .json() method on response object\n\tif (\"json\" in res && typeof (res as any).json === \"function\") {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: Calling dynamic .json() method\n\t\t(res as any).json(snapshot);\n\t} else {\n\t\tres.writeHead(200, { \"Content-Type\": \"application/json\" });\n\t\tres.end(JSON.stringify(snapshot));\n\t}\n}\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport type { ZunoStateEvent } from \"../sync\";\nimport { applyStateEvent } from \"./apply-state-event\";\nimport {\n\tgetEventsAfter,\n\tgetUniverseState,\n\tsubscribeToStateEvents,\n} from \"./core\";\n\ntype IncomingHeaders = IncomingMessage[\"headers\"];\n\n/**\n * Creates a Server-Sent Events (SSE) connection for Zuno state updates.\n */\nexport const createSSEConnection = (\n\treq: IncomingMessage,\n\tres: ServerResponse,\n\theaders: IncomingHeaders,\n) => {\n\tres.writeHead(200, {\n\t\t\"Cache-Control\": \"no-cache, no-transform\",\n\t\t\"Content-Type\": \"text/event-stream; charset=utf-8\",\n\t\tConnection: \"keep-alive\",\n\t\t\"X-Accel-Buffering\": \"no\",\n\t\t...headers,\n\t});\n\n\tres.flushHeaders?.();\n\n\tconst raw =\n\t\treq.headers[\"last-event-id\"] ||\n\t\tnew URL(req.url || \"\", \"http://localhost\").searchParams.get(\"lastEventId\");\n\tconst lastEventId =\n\t\tNumber.parseInt(Array.isArray(raw) ? raw[0] : (raw ?? \"0\"), 10) || 0;\n\n\tif (lastEventId > 0) {\n\t\tconst missed = getEventsAfter(lastEventId);\n\t\tfor (const event of missed) {\n\t\t\tres.write(`id: ${event.eventId}\\n`);\n\t\t\tres.write(`event: state\\n`);\n\t\t\tres.write(`data: ${JSON.stringify(event)}\\n\\n`);\n\t\t}\n\t} else {\n\t\tres.write(`event: snapshot\\n`);\n\t\tres.write(`data: ${JSON.stringify(getUniverseState())}\\n\\n`);\n\t}\n\n\tconst unsubscribe = subscribeToStateEvents((event: ZunoStateEvent) => {\n\t\tres.write(`id: ${event.eventId}\\n`);\n\t\tres.write(`event: state\\n`);\n\t\tres.write(`data: ${JSON.stringify(event)}\\n\\n`);\n\t});\n\n\tconst heartbeat = setInterval(() => {\n\t\tres.write(`: ping ${Date.now()}\\n\\n`);\n\t}, 15000);\n\n\tres.write(\": connected \\n\\n\");\n\n\treq.on(\"close\", () => {\n\t\tclearInterval(heartbeat);\n\t\tunsubscribe();\n\t\tres.end();\n\t});\n};\n\n/**\n * Synchronizes the Zuno universe state by applying an incoming event.\n */\nexport const syncUniverseState = (\n\treq: IncomingMessage,\n\tres: ServerResponse,\n) => {\n\tconst MAX_BODY_BYTES = 512 * 1024; // 512KB safety\n\tlet body = \"\";\n\n\treq.on(\"data\", (chunk: Buffer) => {\n\t\tbody += chunk.toString(\"utf8\");\n\t\tif (body.length > MAX_BODY_BYTES) {\n\t\t\tres.writeHead(413, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ ok: false, reason: \"PAYLOAD_TOO_LARGE\" }));\n\t\t\treq.destroy();\n\t\t}\n\t});\n\n\treq.on(\"end\", () => {\n\t\ttry {\n\t\t\tconst incoming: ZunoStateEvent = JSON.parse(\n\t\t\t\tbody || \"{}\",\n\t\t\t) as unknown as ZunoStateEvent;\n\t\t\tconst result = applyStateEvent(incoming);\n\n\t\t\tif (!result.ok) {\n\t\t\t\tif (result.reason === \"VERSION_CONFLICT\") {\n\t\t\t\t\tres.writeHead(409, { \"Content-Type\": \"application/json\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\treason: \"VERSION_CONFLICT\",\n\t\t\t\t\t\t\tcurrent: result.current,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ ok: true, event: result.event }));\n\t\t} catch {\n\t\t\tres.writeHead(400, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ ok: false, reason: \"INVALID_JSON\" }));\n\t\t}\n\t});\n};\n\nexport const setUniverseState = (req: IncomingMessage, res: ServerResponse) => {\n\treturn syncUniverseState(req, res);\n};\n"]}
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import { f as ZunoStateEvent } from '../index-
|
|
1
|
+
import { f as ZunoStateEvent } from '../index-BIwJxy6r.cjs';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
3
3
|
|
|
4
|
-
type UniverseRecord = {
|
|
5
|
-
state: any;
|
|
6
|
-
version: number;
|
|
7
|
-
};
|
|
8
|
-
type ZunoStateListener = (event: ZunoStateEvent) => void;
|
|
9
|
-
declare const getUniverseRecord: (storeKey: string) => UniverseRecord | undefined;
|
|
10
|
-
declare const updateUniverseState: (event: ZunoStateEvent) => void;
|
|
11
|
-
declare const getUniverseState: () => {
|
|
12
|
-
[k: string]: UniverseRecord;
|
|
13
|
-
};
|
|
14
|
-
declare const appendEvent: (event: ZunoStateEvent) => ZunoStateEvent;
|
|
15
|
-
declare const getEventsAfter: (lastEventId: number) => ZunoStateEvent[];
|
|
16
|
-
declare const getLastEventId: () => number;
|
|
17
|
-
declare const subscribeToStateEvents: (listener: ZunoStateListener) => () => void;
|
|
18
|
-
declare const publishToStateEvent: (event: ZunoStateEvent) => void;
|
|
19
|
-
|
|
20
4
|
type ApplyResult = {
|
|
21
5
|
ok: true;
|
|
22
6
|
event: ZunoStateEvent;
|
|
@@ -24,7 +8,7 @@ type ApplyResult = {
|
|
|
24
8
|
ok: false;
|
|
25
9
|
reason: "VERSION_CONFLICT";
|
|
26
10
|
current: {
|
|
27
|
-
state:
|
|
11
|
+
state: unknown;
|
|
28
12
|
version: number;
|
|
29
13
|
};
|
|
30
14
|
} | {
|
|
@@ -37,6 +21,28 @@ type ApplyResult = {
|
|
|
37
21
|
*/
|
|
38
22
|
declare function applyStateEvent(incoming: ZunoStateEvent): ApplyResult;
|
|
39
23
|
|
|
24
|
+
type UniverseRecord = {
|
|
25
|
+
state: unknown;
|
|
26
|
+
version: number;
|
|
27
|
+
};
|
|
28
|
+
type ZunoStateListener = (event: ZunoStateEvent) => void;
|
|
29
|
+
declare const getUniverseRecord: (storeKey: string) => UniverseRecord | undefined;
|
|
30
|
+
declare const updateUniverseState: (event: ZunoStateEvent) => void;
|
|
31
|
+
declare const getUniverseState: () => {
|
|
32
|
+
[k: string]: UniverseRecord;
|
|
33
|
+
};
|
|
34
|
+
declare const appendEvent: (event: ZunoStateEvent) => ZunoStateEvent;
|
|
35
|
+
declare const getEventsAfter: (lastEventId: number) => ZunoStateEvent[];
|
|
36
|
+
declare const getLastEventId: () => number;
|
|
37
|
+
declare const subscribeToStateEvents: (listener: ZunoStateListener) => () => void;
|
|
38
|
+
declare const publishToStateEvent: (event: ZunoStateEvent) => void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Sends a snapshot of the current universe state to the response.
|
|
42
|
+
* Compatible with both Express and raw Node.js http.
|
|
43
|
+
*/
|
|
44
|
+
declare function sendSnapshot(_req: IncomingMessage, res: ServerResponse): void;
|
|
45
|
+
|
|
40
46
|
type IncomingHeaders = IncomingMessage["headers"];
|
|
41
47
|
/**
|
|
42
48
|
* Creates a Server-Sent Events (SSE) connection for Zuno state updates.
|
|
@@ -48,10 +54,4 @@ declare const createSSEConnection: (req: IncomingMessage, res: ServerResponse, h
|
|
|
48
54
|
declare const syncUniverseState: (req: IncomingMessage, res: ServerResponse) => void;
|
|
49
55
|
declare const setUniverseState: (req: IncomingMessage, res: ServerResponse) => void;
|
|
50
56
|
|
|
51
|
-
/**
|
|
52
|
-
* Sends a snapshot of the current universe state to the response.
|
|
53
|
-
* Compatible with both Express and raw Node.js http.
|
|
54
|
-
*/
|
|
55
|
-
declare function sendSnapshot(_req: IncomingMessage, res: ServerResponse): void;
|
|
56
|
-
|
|
57
57
|
export { type ApplyResult, type UniverseRecord, type ZunoStateListener, appendEvent, applyStateEvent, createSSEConnection, getEventsAfter, getLastEventId, getUniverseRecord, getUniverseState, publishToStateEvent, sendSnapshot, setUniverseState, subscribeToStateEvents, syncUniverseState, updateUniverseState };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import { f as ZunoStateEvent } from '../index-
|
|
1
|
+
import { f as ZunoStateEvent } from '../index-BIwJxy6r.js';
|
|
2
2
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
3
3
|
|
|
4
|
-
type UniverseRecord = {
|
|
5
|
-
state: any;
|
|
6
|
-
version: number;
|
|
7
|
-
};
|
|
8
|
-
type ZunoStateListener = (event: ZunoStateEvent) => void;
|
|
9
|
-
declare const getUniverseRecord: (storeKey: string) => UniverseRecord | undefined;
|
|
10
|
-
declare const updateUniverseState: (event: ZunoStateEvent) => void;
|
|
11
|
-
declare const getUniverseState: () => {
|
|
12
|
-
[k: string]: UniverseRecord;
|
|
13
|
-
};
|
|
14
|
-
declare const appendEvent: (event: ZunoStateEvent) => ZunoStateEvent;
|
|
15
|
-
declare const getEventsAfter: (lastEventId: number) => ZunoStateEvent[];
|
|
16
|
-
declare const getLastEventId: () => number;
|
|
17
|
-
declare const subscribeToStateEvents: (listener: ZunoStateListener) => () => void;
|
|
18
|
-
declare const publishToStateEvent: (event: ZunoStateEvent) => void;
|
|
19
|
-
|
|
20
4
|
type ApplyResult = {
|
|
21
5
|
ok: true;
|
|
22
6
|
event: ZunoStateEvent;
|
|
@@ -24,7 +8,7 @@ type ApplyResult = {
|
|
|
24
8
|
ok: false;
|
|
25
9
|
reason: "VERSION_CONFLICT";
|
|
26
10
|
current: {
|
|
27
|
-
state:
|
|
11
|
+
state: unknown;
|
|
28
12
|
version: number;
|
|
29
13
|
};
|
|
30
14
|
} | {
|
|
@@ -37,6 +21,28 @@ type ApplyResult = {
|
|
|
37
21
|
*/
|
|
38
22
|
declare function applyStateEvent(incoming: ZunoStateEvent): ApplyResult;
|
|
39
23
|
|
|
24
|
+
type UniverseRecord = {
|
|
25
|
+
state: unknown;
|
|
26
|
+
version: number;
|
|
27
|
+
};
|
|
28
|
+
type ZunoStateListener = (event: ZunoStateEvent) => void;
|
|
29
|
+
declare const getUniverseRecord: (storeKey: string) => UniverseRecord | undefined;
|
|
30
|
+
declare const updateUniverseState: (event: ZunoStateEvent) => void;
|
|
31
|
+
declare const getUniverseState: () => {
|
|
32
|
+
[k: string]: UniverseRecord;
|
|
33
|
+
};
|
|
34
|
+
declare const appendEvent: (event: ZunoStateEvent) => ZunoStateEvent;
|
|
35
|
+
declare const getEventsAfter: (lastEventId: number) => ZunoStateEvent[];
|
|
36
|
+
declare const getLastEventId: () => number;
|
|
37
|
+
declare const subscribeToStateEvents: (listener: ZunoStateListener) => () => void;
|
|
38
|
+
declare const publishToStateEvent: (event: ZunoStateEvent) => void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Sends a snapshot of the current universe state to the response.
|
|
42
|
+
* Compatible with both Express and raw Node.js http.
|
|
43
|
+
*/
|
|
44
|
+
declare function sendSnapshot(_req: IncomingMessage, res: ServerResponse): void;
|
|
45
|
+
|
|
40
46
|
type IncomingHeaders = IncomingMessage["headers"];
|
|
41
47
|
/**
|
|
42
48
|
* Creates a Server-Sent Events (SSE) connection for Zuno state updates.
|
|
@@ -48,10 +54,4 @@ declare const createSSEConnection: (req: IncomingMessage, res: ServerResponse, h
|
|
|
48
54
|
declare const syncUniverseState: (req: IncomingMessage, res: ServerResponse) => void;
|
|
49
55
|
declare const setUniverseState: (req: IncomingMessage, res: ServerResponse) => void;
|
|
50
56
|
|
|
51
|
-
/**
|
|
52
|
-
* Sends a snapshot of the current universe state to the response.
|
|
53
|
-
* Compatible with both Express and raw Node.js http.
|
|
54
|
-
*/
|
|
55
|
-
declare function sendSnapshot(_req: IncomingMessage, res: ServerResponse): void;
|
|
56
|
-
|
|
57
57
|
export { type ApplyResult, type UniverseRecord, type ZunoStateListener, appendEvent, applyStateEvent, createSSEConnection, getEventsAfter, getLastEventId, getUniverseRecord, getUniverseState, publishToStateEvent, sendSnapshot, setUniverseState, subscribeToStateEvents, syncUniverseState, updateUniverseState };
|
package/dist/server/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
var p=new Map,
|
|
1
|
+
var p=new Map,d=t=>p.get(t),f=t=>{let e=p.get(t.storeKey)??{version:0},o=typeof t.version=="number"?t.version:e.version+1;p.set(t.storeKey,{state:t.state,version:o});},c=()=>Object.fromEntries(p),h=1e3,w=1,a=[],S=t=>(t.eventId=w++,a.push(t),a.length>h&&a.shift(),t),g=t=>a.filter(e=>(e?.eventId??0)>t),l=()=>a[a.length-1]?.eventId??0,v=new Set,y=t=>(v.add(t),()=>{v.delete(t);}),E=t=>{v.forEach(e=>{e(t);});};function m(t){let e=d(t.storeKey)??{state:void 0,version:0};if(typeof t.baseVersion=="number"&&t.baseVersion!==e.version)return {ok:false,reason:"VERSION_CONFLICT",current:e};let o=e.version+1,n={...t,version:o};return f(n),S(n),E(n),{ok:true,event:n}}function C(t,e){let o={state:c(),lastEventId:l()};"json"in e&&typeof e.json=="function"?e.json(o):(e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(o)));}var A=(t,e,o)=>{e.writeHead(200,{"Cache-Control":"no-cache, no-transform","Content-Type":"text/event-stream; charset=utf-8",Connection:"keep-alive","X-Accel-Buffering":"no",...o}),e.flushHeaders?.();let n=t.headers["last-event-id"]||new URL(t.url||"","http://localhost").searchParams.get("lastEventId"),r=Number.parseInt(Array.isArray(n)?n[0]:n??"0",10)||0;if(r>0){let i=g(r);for(let u of i)e.write(`id: ${u.eventId}
|
|
2
2
|
`),e.write(`event: state
|
|
3
3
|
`),e.write(`data: ${JSON.stringify(u)}
|
|
4
4
|
|
|
5
5
|
`);}else e.write(`event: snapshot
|
|
6
6
|
`),e.write(`data: ${JSON.stringify(c())}
|
|
7
7
|
|
|
8
|
-
`);let s=
|
|
8
|
+
`);let s=y(i=>{e.write(`id: ${i.eventId}
|
|
9
9
|
`),e.write(`event: state
|
|
10
10
|
`),e.write(`data: ${JSON.stringify(i)}
|
|
11
11
|
|
|
@@ -13,5 +13,5 @@ var p=new Map,f=t=>p.get(t),d=t=>{let e=p.get(t.storeKey)??{version:0},o=typeof
|
|
|
13
13
|
|
|
14
14
|
`);},15e3);e.write(`: connected
|
|
15
15
|
|
|
16
|
-
`),t.on("close",()=>{clearInterval(I),s(),e.end();});},
|
|
16
|
+
`),t.on("close",()=>{clearInterval(I),s(),e.end();});},x=(t,e)=>{let n="";t.on("data",r=>{n+=r.toString("utf8"),n.length>524288&&(e.writeHead(413,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:false,reason:"PAYLOAD_TOO_LARGE"})),t.destroy());}),t.on("end",()=>{try{let r=JSON.parse(n||"{}"),s=m(r);if(!s.ok){s.reason==="VERSION_CONFLICT"&&(e.writeHead(409,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:!1,reason:"VERSION_CONFLICT",current:s.current})));return}e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:!0,event:s.event}));}catch{e.writeHead(400,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:false,reason:"INVALID_JSON"}));}});},L=(t,e)=>x(t,e);export{S as appendEvent,m as applyStateEvent,A as createSSEConnection,g as getEventsAfter,l as getLastEventId,d as getUniverseRecord,c as getUniverseState,E as publishToStateEvent,C as sendSnapshot,L as setUniverseState,y as subscribeToStateEvents,x as syncUniverseState,f as updateUniverseState};//# sourceMappingURL=index.js.map
|
|
17
17
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/core.ts","../../src/server/apply-state-event.ts","../../src/server/sse-handler.ts","../../src/server/snapshot-handler.ts"],"names":["universeState","getUniverseRecord","storeKey","updateUniverseState","event","current","nextVersion","getUniverseState","MAX_EVENTS","nextEventId","eventLog","appendEvent","getEventsAfter","lastEventId","getLastEventId","listeners","subscribeToStateEvents","listener","publishToStateEvent","applyStateEvent","incoming","createSSEConnection","req","res","headers","raw","missed","unsubscribe","heartbeat","syncUniverseState","body","chunk","result","setUniverseState","sendSnapshot","_req","snapshot"],"mappings":"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","file":"index.js","sourcesContent":["import type { ZunoStateEvent } from \"../sync\";\n\n// --- Types ---\n\nexport type UniverseRecord = {\n state: any;\n version: number;\n};\n\nexport type ZunoStateListener = (event: ZunoStateEvent) => void;\n\n// --- State Store ---\n\nconst universeState = new Map<string, UniverseRecord>();\n\nexport const getUniverseRecord = (storeKey: string): UniverseRecord | undefined => {\n return universeState.get(storeKey);\n};\n\nexport const updateUniverseState = (event: ZunoStateEvent) => {\n const current = universeState.get(event.storeKey) ?? { state: undefined, version: 0 };\n const nextVersion = typeof event.version === \"number\" ? event.version : current.version + 1;\n universeState.set(event.storeKey, { state: event.state, version: nextVersion });\n};\n\nexport const getUniverseState = () => {\n return Object.fromEntries(universeState);\n};\n\n// --- Event Log ---\n\nconst MAX_EVENTS = 1000;\nlet nextEventId = 1;\nconst eventLog: ZunoStateEvent[] = [];\n\nexport const appendEvent = (event: ZunoStateEvent) => {\n event.eventId = nextEventId++;\n eventLog.push(event);\n if (eventLog.length > MAX_EVENTS) {\n eventLog.shift();\n }\n return event;\n};\n\nexport const getEventsAfter = (lastEventId: number) => {\n return eventLog.filter((event) => (event?.eventId ?? 0) > lastEventId);\n};\n\nexport const getLastEventId = () => {\n return eventLog[eventLog.length - 1]?.eventId ?? 0;\n};\n\n// --- State Bus (Events) ---\n\nconst listeners = new Set<ZunoStateListener>();\n\nexport const subscribeToStateEvents = (listener: ZunoStateListener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n};\n\nexport const publishToStateEvent = (event: ZunoStateEvent) => {\n listeners.forEach((listener) => listener(event));\n};\n","import { getUniverseRecord, updateUniverseState, publishToStateEvent, appendEvent } from \"./core\";\nimport type { ZunoStateEvent } from \"../sync\";\n\nexport type ApplyResult = \n | { ok: true; event: ZunoStateEvent }\n | { ok: false; reason: \"VERSION_CONFLICT\"; current: { state: any; version: number } }\n | { ok: false; reason: string; current?: never };\n\n/**\n * Validates and applies a state event to the server universe.\n */\nexport function applyStateEvent(incoming: ZunoStateEvent): ApplyResult {\n const current = getUniverseRecord(incoming.storeKey) ?? { state: undefined, version: 0 };\n\n // Strict version check\n if (typeof incoming.baseVersion === \"number\" && incoming.baseVersion !== current.version) {\n return { ok: false, reason: \"VERSION_CONFLICT\", current };\n }\n\n // Increment version\n const nextVersion = current.version + 1;\n const event = { ...incoming, version: nextVersion };\n\n // Persistence\n updateUniverseState(event);\n appendEvent(event);\n\n // Notify SSE subscribers\n publishToStateEvent(event);\n\n return { ok: true, event };\n}\n","import { subscribeToStateEvents } from \"./core\";\nimport { getUniverseState } from \"./core\";\nimport { getEventsAfter } from \"./core\";\nimport type { ZunoStateEvent } from \"../sync\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { applyStateEvent } from \"./apply-state-event\";\n\ntype IncomingHeaders = IncomingMessage[\"headers\"];\n\n/**\n * Creates a Server-Sent Events (SSE) connection for Zuno state updates.\n */\nexport const createSSEConnection = (req: IncomingMessage, res: ServerResponse, headers: IncomingHeaders) => {\n res.writeHead(200, {\n \"Cache-Control\": \"no-cache, no-transform\",\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\",\n ...headers\n });\n\n res.flushHeaders?.();\n\n const raw = req.headers[\"last-event-id\"] || new URL(req.url || \"\", \"http://localhost\").searchParams.get(\"lastEventId\");\n const lastEventId = Number.parseInt(Array.isArray(raw) ? raw[0] : (raw ?? \"0\"), 10) || 0;\n\n if (lastEventId > 0) {\n const missed = getEventsAfter(lastEventId);\n for (const event of missed) {\n res.write(`id: ${event.eventId}\\n`);\n res.write(`event: state\\n`);\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n }\n } else {\n res.write(`event: snapshot\\n`);\n res.write(`data: ${JSON.stringify(getUniverseState())}\\n\\n`);\n }\n\n const unsubscribe = subscribeToStateEvents((event: ZunoStateEvent) => {\n res.write(`id: ${event.eventId}\\n`);\n res.write(`event: state\\n`);\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n });\n\n const heartbeat = setInterval(() => {\n res.write(`: ping ${Date.now()}\\n\\n`);\n }, 15000);\n\n res.write(\": connected \\n\\n\");\n\n req.on(\"close\", () => {\n clearInterval(heartbeat);\n unsubscribe();\n res.end();\n });\n};\n\n/**\n * Synchronizes the Zuno universe state by applying an incoming event.\n */\nexport const syncUniverseState = (req: IncomingMessage, res: ServerResponse) => {\n const MAX_BODY_BYTES = 512 * 1024; // 512KB safety\n let body = \"\";\n\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString(\"utf8\");\n if (body.length > MAX_BODY_BYTES) {\n res.writeHead(413, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, reason: \"PAYLOAD_TOO_LARGE\" }));\n req.destroy();\n }\n });\n\n req.on(\"end\", () => {\n try {\n const incoming: ZunoStateEvent = JSON.parse(body || \"{}\") as any;\n const result = applyStateEvent(incoming);\n\n if (!result.ok) {\n if (result.reason === \"VERSION_CONFLICT\") {\n res.writeHead(409, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, reason: \"VERSION_CONFLICT\", current: result.current }));\n }\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true, event: result.event }));\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: false, reason: \"INVALID_JSON\" }));\n }\n });\n};\n\nexport const setUniverseState = (req: IncomingMessage, res: ServerResponse) => {\n return syncUniverseState(req, res);\n};\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport { getUniverseState, getLastEventId } from \"./core\";\n\n/**\n * Sends a snapshot of the current universe state to the response.\n * Compatible with both Express and raw Node.js http.\n */\nexport function sendSnapshot(_req: IncomingMessage, res: ServerResponse) {\n const snapshot = {\n state: getUniverseState(),\n lastEventId: getLastEventId(),\n };\n\n // Check for Express-like .json() method\n if (\"json\" in res && typeof (res as any).json === \"function\") {\n (res as any).json(snapshot);\n } else {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(snapshot));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/core.ts","../../src/server/apply-state-event.ts","../../src/server/snapshot-handler.ts","../../src/server/sse-handler.ts"],"names":["universeState","getUniverseRecord","storeKey","updateUniverseState","event","current","nextVersion","getUniverseState","MAX_EVENTS","nextEventId","eventLog","appendEvent","getEventsAfter","lastEventId","getLastEventId","listeners","subscribeToStateEvents","listener","publishToStateEvent","applyStateEvent","incoming","sendSnapshot","_req","res","snapshot","createSSEConnection","req","headers","raw","missed","unsubscribe","heartbeat","syncUniverseState","body","chunk","result","setUniverseState"],"mappings":"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","file":"index.js","sourcesContent":["import type { ZunoStateEvent } from \"../sync\";\n\n// --- Types ---\n\nexport type UniverseRecord = {\n\tstate: unknown; // Was 'any'\n\tversion: number;\n};\n\nexport type ZunoStateListener = (event: ZunoStateEvent) => void;\n\n// --- State Store ---\n\nconst universeState = new Map<string, UniverseRecord>();\n\nexport const getUniverseRecord = (\n\tstoreKey: string,\n): UniverseRecord | undefined => {\n\treturn universeState.get(storeKey);\n};\n\nexport const updateUniverseState = (event: ZunoStateEvent) => {\n\tconst current = universeState.get(event.storeKey) ?? {\n\t\tstate: undefined,\n\t\tversion: 0,\n\t};\n\tconst nextVersion =\n\t\ttypeof event.version === \"number\" ? event.version : current.version + 1;\n\tuniverseState.set(event.storeKey, {\n\t\tstate: event.state,\n\t\tversion: nextVersion,\n\t});\n};\n\nexport const getUniverseState = () => {\n\treturn Object.fromEntries(universeState);\n};\n\n// --- Event Log ---\n\nconst MAX_EVENTS = 1000;\nlet nextEventId = 1;\nconst eventLog: ZunoStateEvent[] = [];\n\nexport const appendEvent = (event: ZunoStateEvent) => {\n\tevent.eventId = nextEventId++;\n\teventLog.push(event);\n\tif (eventLog.length > MAX_EVENTS) {\n\t\teventLog.shift();\n\t}\n\treturn event;\n};\n\nexport const getEventsAfter = (lastEventId: number) => {\n\treturn eventLog.filter((event) => (event?.eventId ?? 0) > lastEventId);\n};\n\nexport const getLastEventId = () => {\n\treturn eventLog[eventLog.length - 1]?.eventId ?? 0;\n};\n\n// --- State Bus (Events) ---\n\nconst listeners = new Set<ZunoStateListener>();\n\nexport const subscribeToStateEvents = (listener: ZunoStateListener) => {\n\tlisteners.add(listener);\n\treturn () => {\n\t\tlisteners.delete(listener);\n\t};\n};\n\nexport const publishToStateEvent = (event: ZunoStateEvent) => {\n\tlisteners.forEach((listener) => {\n\t\tlistener(event);\n\t});\n};\n","import type { ZunoStateEvent } from \"../sync\";\nimport {\n\tappendEvent,\n\tgetUniverseRecord,\n\tpublishToStateEvent,\n\tupdateUniverseState,\n} from \"./core\";\n\nexport type ApplyResult =\n\t| { ok: true; event: ZunoStateEvent }\n\t| {\n\t\t\tok: false;\n\t\t\treason: \"VERSION_CONFLICT\";\n\t\t\tcurrent: { state: unknown; version: number };\n\t }\n\t| { ok: false; reason: string; current?: never };\n\n/**\n * Validates and applies a state event to the server universe.\n */\nexport function applyStateEvent(incoming: ZunoStateEvent): ApplyResult {\n\tconst current = getUniverseRecord(incoming.storeKey) ?? {\n\t\tstate: undefined,\n\t\tversion: 0,\n\t};\n\n\t// Strict version check\n\tif (\n\t\ttypeof incoming.baseVersion === \"number\" &&\n\t\tincoming.baseVersion !== current.version\n\t) {\n\t\treturn { ok: false, reason: \"VERSION_CONFLICT\", current };\n\t}\n\n\t// Increment version\n\tconst nextVersion = current.version + 1;\n\tconst event = { ...incoming, version: nextVersion };\n\n\t// Persistence\n\tupdateUniverseState(event);\n\tappendEvent(event);\n\n\t// Notify SSE subscribers\n\tpublishToStateEvent(event);\n\n\treturn { ok: true, event };\n}\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport { getLastEventId, getUniverseState } from \"./core\";\n\n/**\n * Sends a snapshot of the current universe state to the response.\n * Compatible with both Express and raw Node.js http.\n */\nexport function sendSnapshot(_req: IncomingMessage, res: ServerResponse) {\n\tconst snapshot = {\n\t\tstate: getUniverseState(),\n\t\tlastEventId: getLastEventId(),\n\t};\n\n\t// Check for Express-like .json() method\n\t// biome-ignore lint/suspicious/noExplicitAny: Checking for dynamic .json() method on response object\n\tif (\"json\" in res && typeof (res as any).json === \"function\") {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: Calling dynamic .json() method\n\t\t(res as any).json(snapshot);\n\t} else {\n\t\tres.writeHead(200, { \"Content-Type\": \"application/json\" });\n\t\tres.end(JSON.stringify(snapshot));\n\t}\n}\n","import type { IncomingMessage, ServerResponse } from \"http\";\nimport type { ZunoStateEvent } from \"../sync\";\nimport { applyStateEvent } from \"./apply-state-event\";\nimport {\n\tgetEventsAfter,\n\tgetUniverseState,\n\tsubscribeToStateEvents,\n} from \"./core\";\n\ntype IncomingHeaders = IncomingMessage[\"headers\"];\n\n/**\n * Creates a Server-Sent Events (SSE) connection for Zuno state updates.\n */\nexport const createSSEConnection = (\n\treq: IncomingMessage,\n\tres: ServerResponse,\n\theaders: IncomingHeaders,\n) => {\n\tres.writeHead(200, {\n\t\t\"Cache-Control\": \"no-cache, no-transform\",\n\t\t\"Content-Type\": \"text/event-stream; charset=utf-8\",\n\t\tConnection: \"keep-alive\",\n\t\t\"X-Accel-Buffering\": \"no\",\n\t\t...headers,\n\t});\n\n\tres.flushHeaders?.();\n\n\tconst raw =\n\t\treq.headers[\"last-event-id\"] ||\n\t\tnew URL(req.url || \"\", \"http://localhost\").searchParams.get(\"lastEventId\");\n\tconst lastEventId =\n\t\tNumber.parseInt(Array.isArray(raw) ? raw[0] : (raw ?? \"0\"), 10) || 0;\n\n\tif (lastEventId > 0) {\n\t\tconst missed = getEventsAfter(lastEventId);\n\t\tfor (const event of missed) {\n\t\t\tres.write(`id: ${event.eventId}\\n`);\n\t\t\tres.write(`event: state\\n`);\n\t\t\tres.write(`data: ${JSON.stringify(event)}\\n\\n`);\n\t\t}\n\t} else {\n\t\tres.write(`event: snapshot\\n`);\n\t\tres.write(`data: ${JSON.stringify(getUniverseState())}\\n\\n`);\n\t}\n\n\tconst unsubscribe = subscribeToStateEvents((event: ZunoStateEvent) => {\n\t\tres.write(`id: ${event.eventId}\\n`);\n\t\tres.write(`event: state\\n`);\n\t\tres.write(`data: ${JSON.stringify(event)}\\n\\n`);\n\t});\n\n\tconst heartbeat = setInterval(() => {\n\t\tres.write(`: ping ${Date.now()}\\n\\n`);\n\t}, 15000);\n\n\tres.write(\": connected \\n\\n\");\n\n\treq.on(\"close\", () => {\n\t\tclearInterval(heartbeat);\n\t\tunsubscribe();\n\t\tres.end();\n\t});\n};\n\n/**\n * Synchronizes the Zuno universe state by applying an incoming event.\n */\nexport const syncUniverseState = (\n\treq: IncomingMessage,\n\tres: ServerResponse,\n) => {\n\tconst MAX_BODY_BYTES = 512 * 1024; // 512KB safety\n\tlet body = \"\";\n\n\treq.on(\"data\", (chunk: Buffer) => {\n\t\tbody += chunk.toString(\"utf8\");\n\t\tif (body.length > MAX_BODY_BYTES) {\n\t\t\tres.writeHead(413, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ ok: false, reason: \"PAYLOAD_TOO_LARGE\" }));\n\t\t\treq.destroy();\n\t\t}\n\t});\n\n\treq.on(\"end\", () => {\n\t\ttry {\n\t\t\tconst incoming: ZunoStateEvent = JSON.parse(\n\t\t\t\tbody || \"{}\",\n\t\t\t) as unknown as ZunoStateEvent;\n\t\t\tconst result = applyStateEvent(incoming);\n\n\t\t\tif (!result.ok) {\n\t\t\t\tif (result.reason === \"VERSION_CONFLICT\") {\n\t\t\t\t\tres.writeHead(409, { \"Content-Type\": \"application/json\" });\n\t\t\t\t\tres.end(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\treason: \"VERSION_CONFLICT\",\n\t\t\t\t\t\t\tcurrent: result.current,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ ok: true, event: result.event }));\n\t\t} catch {\n\t\t\tres.writeHead(400, { \"Content-Type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify({ ok: false, reason: \"INVALID_JSON\" }));\n\t\t}\n\t});\n};\n\nexport const setUniverseState = (req: IncomingMessage, res: ServerResponse) => {\n\treturn syncUniverseState(req, res);\n};\n"]}
|