@iadev93/zuno 0.0.5 → 0.0.7
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/LICENSE +9 -0
- package/README.md +7 -11
- package/dist/index-IbvF6TBr.d.cts +151 -0
- package/dist/index-IbvF6TBr.d.ts +151 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{shared/readable.d.ts → index.d.cts} +7 -4
- package/dist/index.d.ts +24 -10
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +17 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +57 -0
- package/dist/server/index.d.ts +57 -4
- package/dist/server/index.js +17 -3
- package/dist/server/index.js.map +1 -0
- package/package.json +9 -5
- package/dist/core/createZuno.d.ts +0 -77
- package/dist/core/createZuno.d.ts.map +0 -1
- package/dist/core/createZuno.js +0 -250
- package/dist/core/store.d.ts +0 -10
- package/dist/core/store.d.ts.map +0 -1
- package/dist/core/store.js +0 -27
- package/dist/core/types.d.ts +0 -107
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -1
- package/dist/core/universe.d.ts +0 -12
- package/dist/core/universe.d.ts.map +0 -1
- package/dist/core/universe.js +0 -53
- package/dist/index.d.ts.map +0 -1
- package/dist/server/apply-state-event.d.ts +0 -26
- package/dist/server/apply-state-event.d.ts.map +0 -1
- package/dist/server/apply-state-event.js +0 -29
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/server-transport.d.ts +0 -8
- package/dist/server/server-transport.d.ts.map +0 -1
- package/dist/server/server-transport.js +0 -25
- package/dist/server/snapshot-handler.d.ts +0 -9
- package/dist/server/snapshot-handler.d.ts.map +0 -1
- package/dist/server/snapshot-handler.js +0 -18
- package/dist/server/sse-handler.d.ts +0 -24
- package/dist/server/sse-handler.d.ts.map +0 -1
- package/dist/server/sse-handler.js +0 -127
- package/dist/server/state.bus.d.ts +0 -18
- package/dist/server/state.bus.d.ts.map +0 -1
- package/dist/server/state.bus.js +0 -26
- package/dist/server/state.log.d.ts +0 -22
- package/dist/server/state.log.d.ts.map +0 -1
- package/dist/server/state.log.js +0 -37
- package/dist/server/types.d.ts +0 -8
- package/dist/server/types.d.ts.map +0 -1
- package/dist/server/types.js +0 -1
- package/dist/server/universe-store.d.ts +0 -29
- package/dist/server/universe-store.d.ts.map +0 -1
- package/dist/server/universe-store.js +0 -26
- package/dist/shared/readable.d.ts.map +0 -1
- package/dist/shared/readable.js +0 -7
- package/dist/sync/apply-incoming-event.d.ts +0 -10
- package/dist/sync/apply-incoming-event.d.ts.map +0 -1
- package/dist/sync/apply-incoming-event.js +0 -28
- package/dist/sync/broadcast-channel.d.ts +0 -12
- package/dist/sync/broadcast-channel.d.ts.map +0 -1
- package/dist/sync/broadcast-channel.js +0 -73
- package/dist/sync/sse-client.d.ts +0 -21
- package/dist/sync/sse-client.d.ts.map +0 -1
- package/dist/sync/sse-client.js +0 -162
- package/dist/sync/sync-types.d.ts +0 -164
- package/dist/sync/sync-types.d.ts.map +0 -1
- package/dist/sync/sync-types.js +0 -1
- package/dist/sync/transport.d.ts +0 -10
- package/dist/sync/transport.d.ts.map +0 -1
- package/dist/sync/transport.js +0 -26
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ibrahim Aftab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
# @iadev93/zuno
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p><b>The core state engine for Zuno.</b></p>
|
|
4
4
|
|
|
5
|
-
Zuno is
|
|
5
|
+
Zuno is a universal, event-driven state system designed to keep **client, server, and multiple runtimes** in sync with strong consistency guarantees.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* Versioned event model
|
|
13
|
-
* Sync primitives (SSE + BroadcastChannel)
|
|
14
|
-
* Adapter contract (`ZunoReadable`) for UI/framework bindings
|
|
15
|
-
* Optional server helpers (snapshot + SSE handlers)
|
|
7
|
+
This package provides the foundation:
|
|
8
|
+
- 🌌 **Universe**: Central coordination for all stores.
|
|
9
|
+
- 📦 **Store**: Versioned, observable state units.
|
|
10
|
+
- 🔄 **Sync Primitives**: SSE and BroadcastChannel transport implementations.
|
|
11
|
+
- 🔌 **Adapter Ready**: Exposes the `ZunoReadable` contract for UI bindings.
|
|
16
12
|
|
|
17
13
|
---
|
|
18
14
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoritative state event.
|
|
3
|
+
*/
|
|
4
|
+
type ZunoStateEvent = {
|
|
5
|
+
storeKey: string;
|
|
6
|
+
state: any;
|
|
7
|
+
version?: number;
|
|
8
|
+
baseVersion?: number;
|
|
9
|
+
origin?: string;
|
|
10
|
+
ts?: number;
|
|
11
|
+
eventId?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Generic transport status.
|
|
15
|
+
*/
|
|
16
|
+
type TransportStatus = {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
status: number;
|
|
19
|
+
json: any;
|
|
20
|
+
reason?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Client transport interface.
|
|
24
|
+
*/
|
|
25
|
+
interface ZunoTransport {
|
|
26
|
+
dispatch(event: ZunoStateEvent): Promise<TransportStatus>;
|
|
27
|
+
unsubscribe?(): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply incoming event to the universe and local bookkeeping.
|
|
31
|
+
*/
|
|
32
|
+
declare function applyIncomingEvent(universe: Universe, event: ZunoStateEvent, context: {
|
|
33
|
+
clientId: string;
|
|
34
|
+
localState: Map<string, unknown>;
|
|
35
|
+
versions: Map<string, number>;
|
|
36
|
+
}): void;
|
|
37
|
+
type SSEOptions = {
|
|
38
|
+
universe: Universe;
|
|
39
|
+
url: string;
|
|
40
|
+
syncUrl: string;
|
|
41
|
+
optimistic: boolean;
|
|
42
|
+
clientId: string;
|
|
43
|
+
versions: Map<string, number>;
|
|
44
|
+
getLastEventId: () => number;
|
|
45
|
+
onOpen?: () => void;
|
|
46
|
+
onClose?: () => void;
|
|
47
|
+
};
|
|
48
|
+
declare function startSSE(opts: SSEOptions): ZunoTransport;
|
|
49
|
+
type BCOptions = {
|
|
50
|
+
channelName: string;
|
|
51
|
+
clientId: string;
|
|
52
|
+
onEvent: (event: ZunoStateEvent) => void;
|
|
53
|
+
getSnapshot: () => Record<string, {
|
|
54
|
+
state: unknown;
|
|
55
|
+
version: number;
|
|
56
|
+
}>;
|
|
57
|
+
onSnapshot: (snap: Record<string, {
|
|
58
|
+
state: unknown;
|
|
59
|
+
version: number;
|
|
60
|
+
}>) => void;
|
|
61
|
+
};
|
|
62
|
+
declare function startBroadcastChannel(opts: BCOptions): {
|
|
63
|
+
publish: (event: ZunoStateEvent) => void;
|
|
64
|
+
hello: () => void;
|
|
65
|
+
stop: () => void;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Authoritative snapshot of the entire universe.
|
|
70
|
+
*/
|
|
71
|
+
type ZunoSnapshot = {
|
|
72
|
+
state: Record<string, {
|
|
73
|
+
state: unknown;
|
|
74
|
+
version: number;
|
|
75
|
+
}>;
|
|
76
|
+
lastEventId: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* A simple state container for a single keyed value.
|
|
80
|
+
*/
|
|
81
|
+
interface Store<T> {
|
|
82
|
+
get(): T;
|
|
83
|
+
set(next: T | ((prev: T) => T)): void;
|
|
84
|
+
subscribe(listener: (state: T) => void): () => void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A Universe manages many stores.
|
|
88
|
+
*/
|
|
89
|
+
interface Universe {
|
|
90
|
+
getStore<T>(key: string, init: () => T): Store<T>;
|
|
91
|
+
snapshot(): Record<string, unknown>;
|
|
92
|
+
restore(data: Record<string, unknown>): void;
|
|
93
|
+
delete(key: string): void;
|
|
94
|
+
clear(): void;
|
|
95
|
+
hydrateSnapshot(snapshot: ZunoSnapshot): void;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Options for creating a Zuno instance.
|
|
99
|
+
*/
|
|
100
|
+
type CreateZunoOptions = {
|
|
101
|
+
/** Optional pre-existing universe. */
|
|
102
|
+
universe?: Universe;
|
|
103
|
+
/** BroadcastChannel name for local tab sync. */
|
|
104
|
+
channelName?: string;
|
|
105
|
+
/** SSE endpoint URL. */
|
|
106
|
+
sseUrl?: string;
|
|
107
|
+
/** Sync endpoint URL (required if sseUrl is provided). */
|
|
108
|
+
syncUrl?: string;
|
|
109
|
+
/** Apply updates locally before server confirmation (default: true). */
|
|
110
|
+
optimistic?: boolean;
|
|
111
|
+
/** Unique client identifier (default: random UUID). */
|
|
112
|
+
clientId?: string;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* An extended interface for a Zuno store that includes methods for setting state
|
|
116
|
+
* and a unique key identifier. This represents a store that has been "bound" or registered.
|
|
117
|
+
*/
|
|
118
|
+
type BoundStore<T> = {
|
|
119
|
+
key: string;
|
|
120
|
+
get: () => T;
|
|
121
|
+
set: (next: T | ((prev: T) => T)) => Promise<any>;
|
|
122
|
+
subscribe: (cb: (state: T) => void) => () => void;
|
|
123
|
+
raw: () => Store<T>;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Creates a raw ZUNO state management store.
|
|
127
|
+
*/
|
|
128
|
+
declare const createStore: <T>(initial: T) => Store<T>;
|
|
129
|
+
/**
|
|
130
|
+
* Creates a ZUNO Universe to manage multiple stores.
|
|
131
|
+
*/
|
|
132
|
+
declare const createUniverse: () => Universe;
|
|
133
|
+
/**
|
|
134
|
+
* Creates a Zuno instance for distributed state synchronization.
|
|
135
|
+
*/
|
|
136
|
+
declare const createZuno: (opts?: CreateZunoOptions) => {
|
|
137
|
+
universe: Universe;
|
|
138
|
+
clientId: string;
|
|
139
|
+
store: <T>(storeKey: string, init: () => T) => BoundStore<T>;
|
|
140
|
+
getStore: <T>(key: string, init: () => T) => Store<T>;
|
|
141
|
+
get: <T>(key: string, init?: () => T) => T;
|
|
142
|
+
set: <T>(key: string, next: T | ((prev: T) => T), init?: () => T) => Promise<TransportStatus>;
|
|
143
|
+
subscribe: <T>(key: string, init: () => T, cb: (state: T) => void) => () => void;
|
|
144
|
+
dispatch: (event: ZunoStateEvent) => Promise<TransportStatus>;
|
|
145
|
+
stop: () => void;
|
|
146
|
+
hydrateSnapshot: (snapshot: ZunoSnapshot) => void;
|
|
147
|
+
getLastEventId: () => number;
|
|
148
|
+
setLastEventId: (id: number) => void;
|
|
149
|
+
};
|
|
150
|
+
|
|
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, createUniverse as a, createStore as b, createZuno as c, startBroadcastChannel as d, applyIncomingEvent as e, type ZunoStateEvent as f, type ZunoTransport as g, startSSE as s };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoritative state event.
|
|
3
|
+
*/
|
|
4
|
+
type ZunoStateEvent = {
|
|
5
|
+
storeKey: string;
|
|
6
|
+
state: any;
|
|
7
|
+
version?: number;
|
|
8
|
+
baseVersion?: number;
|
|
9
|
+
origin?: string;
|
|
10
|
+
ts?: number;
|
|
11
|
+
eventId?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Generic transport status.
|
|
15
|
+
*/
|
|
16
|
+
type TransportStatus = {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
status: number;
|
|
19
|
+
json: any;
|
|
20
|
+
reason?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Client transport interface.
|
|
24
|
+
*/
|
|
25
|
+
interface ZunoTransport {
|
|
26
|
+
dispatch(event: ZunoStateEvent): Promise<TransportStatus>;
|
|
27
|
+
unsubscribe?(): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply incoming event to the universe and local bookkeeping.
|
|
31
|
+
*/
|
|
32
|
+
declare function applyIncomingEvent(universe: Universe, event: ZunoStateEvent, context: {
|
|
33
|
+
clientId: string;
|
|
34
|
+
localState: Map<string, unknown>;
|
|
35
|
+
versions: Map<string, number>;
|
|
36
|
+
}): void;
|
|
37
|
+
type SSEOptions = {
|
|
38
|
+
universe: Universe;
|
|
39
|
+
url: string;
|
|
40
|
+
syncUrl: string;
|
|
41
|
+
optimistic: boolean;
|
|
42
|
+
clientId: string;
|
|
43
|
+
versions: Map<string, number>;
|
|
44
|
+
getLastEventId: () => number;
|
|
45
|
+
onOpen?: () => void;
|
|
46
|
+
onClose?: () => void;
|
|
47
|
+
};
|
|
48
|
+
declare function startSSE(opts: SSEOptions): ZunoTransport;
|
|
49
|
+
type BCOptions = {
|
|
50
|
+
channelName: string;
|
|
51
|
+
clientId: string;
|
|
52
|
+
onEvent: (event: ZunoStateEvent) => void;
|
|
53
|
+
getSnapshot: () => Record<string, {
|
|
54
|
+
state: unknown;
|
|
55
|
+
version: number;
|
|
56
|
+
}>;
|
|
57
|
+
onSnapshot: (snap: Record<string, {
|
|
58
|
+
state: unknown;
|
|
59
|
+
version: number;
|
|
60
|
+
}>) => void;
|
|
61
|
+
};
|
|
62
|
+
declare function startBroadcastChannel(opts: BCOptions): {
|
|
63
|
+
publish: (event: ZunoStateEvent) => void;
|
|
64
|
+
hello: () => void;
|
|
65
|
+
stop: () => void;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Authoritative snapshot of the entire universe.
|
|
70
|
+
*/
|
|
71
|
+
type ZunoSnapshot = {
|
|
72
|
+
state: Record<string, {
|
|
73
|
+
state: unknown;
|
|
74
|
+
version: number;
|
|
75
|
+
}>;
|
|
76
|
+
lastEventId: number;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* A simple state container for a single keyed value.
|
|
80
|
+
*/
|
|
81
|
+
interface Store<T> {
|
|
82
|
+
get(): T;
|
|
83
|
+
set(next: T | ((prev: T) => T)): void;
|
|
84
|
+
subscribe(listener: (state: T) => void): () => void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A Universe manages many stores.
|
|
88
|
+
*/
|
|
89
|
+
interface Universe {
|
|
90
|
+
getStore<T>(key: string, init: () => T): Store<T>;
|
|
91
|
+
snapshot(): Record<string, unknown>;
|
|
92
|
+
restore(data: Record<string, unknown>): void;
|
|
93
|
+
delete(key: string): void;
|
|
94
|
+
clear(): void;
|
|
95
|
+
hydrateSnapshot(snapshot: ZunoSnapshot): void;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Options for creating a Zuno instance.
|
|
99
|
+
*/
|
|
100
|
+
type CreateZunoOptions = {
|
|
101
|
+
/** Optional pre-existing universe. */
|
|
102
|
+
universe?: Universe;
|
|
103
|
+
/** BroadcastChannel name for local tab sync. */
|
|
104
|
+
channelName?: string;
|
|
105
|
+
/** SSE endpoint URL. */
|
|
106
|
+
sseUrl?: string;
|
|
107
|
+
/** Sync endpoint URL (required if sseUrl is provided). */
|
|
108
|
+
syncUrl?: string;
|
|
109
|
+
/** Apply updates locally before server confirmation (default: true). */
|
|
110
|
+
optimistic?: boolean;
|
|
111
|
+
/** Unique client identifier (default: random UUID). */
|
|
112
|
+
clientId?: string;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* An extended interface for a Zuno store that includes methods for setting state
|
|
116
|
+
* and a unique key identifier. This represents a store that has been "bound" or registered.
|
|
117
|
+
*/
|
|
118
|
+
type BoundStore<T> = {
|
|
119
|
+
key: string;
|
|
120
|
+
get: () => T;
|
|
121
|
+
set: (next: T | ((prev: T) => T)) => Promise<any>;
|
|
122
|
+
subscribe: (cb: (state: T) => void) => () => void;
|
|
123
|
+
raw: () => Store<T>;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Creates a raw ZUNO state management store.
|
|
127
|
+
*/
|
|
128
|
+
declare const createStore: <T>(initial: T) => Store<T>;
|
|
129
|
+
/**
|
|
130
|
+
* Creates a ZUNO Universe to manage multiple stores.
|
|
131
|
+
*/
|
|
132
|
+
declare const createUniverse: () => Universe;
|
|
133
|
+
/**
|
|
134
|
+
* Creates a Zuno instance for distributed state synchronization.
|
|
135
|
+
*/
|
|
136
|
+
declare const createZuno: (opts?: CreateZunoOptions) => {
|
|
137
|
+
universe: Universe;
|
|
138
|
+
clientId: string;
|
|
139
|
+
store: <T>(storeKey: string, init: () => T) => BoundStore<T>;
|
|
140
|
+
getStore: <T>(key: string, init: () => T) => Store<T>;
|
|
141
|
+
get: <T>(key: string, init?: () => T) => T;
|
|
142
|
+
set: <T>(key: string, next: T | ((prev: T) => T), init?: () => T) => Promise<TransportStatus>;
|
|
143
|
+
subscribe: <T>(key: string, init: () => T, cb: (state: T) => void) => () => void;
|
|
144
|
+
dispatch: (event: ZunoStateEvent) => Promise<TransportStatus>;
|
|
145
|
+
stop: () => void;
|
|
146
|
+
hydrateSnapshot: (snapshot: ZunoSnapshot) => void;
|
|
147
|
+
getLastEventId: () => number;
|
|
148
|
+
setLastEventId: (id: number) => void;
|
|
149
|
+
};
|
|
150
|
+
|
|
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, createUniverse as a, createStore as b, createZuno as c, startBroadcastChannel as d, applyIncomingEvent as e, type ZunoStateEvent as f, type ZunoTransport as g, startSSE as s };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';function f(r,o,n){let{clientId:t,versions:a}=n;if(o.origin!==t){if(typeof o.version=="number"){let i=a.get(o.storeKey)??0;if(o.version<=i)return;a.set(o.storeKey,o.version);}r.getStore(o.storeKey,()=>o.state).set(o.state);}}function h(r){let{url:o,syncUrl:n,universe:t,clientId:a,versions:i,getLastEventId:g}=r,v=null,d=0;function y(){let p=g(),l=new URL(o,globalThis.location?.href);p>0&&l.searchParams.set("lastEventId",String(p)),v=new EventSource(l.toString()),v.addEventListener("snapshot",S=>{try{let e=JSON.parse(S.data);for(let[s,c]of Object.entries(e)){let u=c;i.set(s,Math.max(i.get(s)??0,u.version)),t.getStore(s,()=>u.state).set(u.state);}}catch(e){console.error("[Zuno] Failed to parse snapshot",e);}}),v.addEventListener("state",S=>{try{let e=JSON.parse(S.data);if(e.origin===a)return;if(typeof e.version=="number"){let s=i.get(e.storeKey)??0;if(e.version<=s)return;i.set(e.storeKey,e.version);}t.getStore(e.storeKey,()=>e.state).set(e.state);}catch(e){console.error("[Zuno] Failed to parse SSE event",e);}}),v.onopen=()=>{d=0,r.onOpen?.();},v.onerror=()=>{v?.close(),r.onClose?.();let S=Math.min(1e3*Math.pow(2,d),3e4);d++,setTimeout(y,S);};}return y(),{dispatch:async p=>{try{r.optimistic&&t.getStore(p.storeKey,()=>p.state).set(p.state);let l=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)});if(l.status===409){let e=await l.json();if(e.current){let{state:s,version:c}=e.current;i.set(p.storeKey,c),t.getStore(p.storeKey,()=>s).set(s);}return {ok:!1,status:409,json:e,reason:"CONFLICT"}}if(!l.ok)return {ok:!1,status:l.status,json:await l.json()};let S=await l.json();if(S.event){let{state:e,version:s}=S.event;typeof s=="number"&&i.set(p.storeKey,s);}return {ok:!0,status:200,json:S}}catch(l){return {ok:false,status:500,json:l,reason:"NETWORK_ERROR"}}},unsubscribe:()=>{v?.close();}}}function T(r){let{channelName:o,clientId:n,onEvent:t,getSnapshot:a,onSnapshot:i}=r,g=new BroadcastChannel(o);return g.onmessage=v=>{let d=v.data;d.origin!==n&&(d.type==="event"&&t(d.event),d.type==="hello"&&g.postMessage({type:"snapshot",snapshot:a(),origin:n}),d.type==="snapshot"&&i(d.snapshot));},{publish:v=>g.postMessage({type:"event",event:v,origin:n}),hello:()=>g.postMessage({type:"hello",origin:n}),stop:()=>g.close()}}var m=r=>{let o=r,n=new Set;return {get:()=>o,set:t=>{let a=typeof t=="function"?t(o):t;Object.is(a,o)||(o=a,n.forEach(i=>i(o)));},subscribe:t=>(n.add(t),()=>n.delete(t))}},E=()=>{let r=new Map;return {getStore(n,t){return r.has(n)||r.set(n,m(t())),r.get(n)},snapshot(){let n={};for(let[t,a]of r.entries())n[t]=a.get();return n},restore(n){for(let[t,a]of Object.entries(n)){let i=r.get(t);i?i.set(a):r.set(t,m(a));}},delete(n){r.delete(n);},clear(){r.clear();},hydrateSnapshot(n){let t={};for(let[a,i]of Object.entries(n.state))t[a]=i.state;this.restore(t);}}},w=(r={})=>{let n=new Map,t=r.universe??E(),a=r.clientId??globalThis.crypto?.randomUUID?.()??String(Math.random()),i=false,g=0;function v(e){let s={};for(let[c,u]of Object.entries(e.state))s[c]=u.state,n.set(c,u.version);t.restore(s),g=e.lastEventId;}let d=e=>{typeof e.eventId=="number"&&(g=Math.max(g,e.eventId)),f(t,e,{clientId:a,versions:n});},y=r.sseUrl&&r.syncUrl?h({universe:t,url:r.sseUrl,syncUrl:r.syncUrl,optimistic:r.optimistic??true,clientId:a,versions:n,getLastEventId:()=>g,onOpen:()=>{i=true;},onClose:()=>{i=false;}}):null,p=r.channelName?T({channelName:r.channelName,clientId:a,onEvent:d,getSnapshot:()=>{let e=t.snapshot(),s={};for(let[c,u]of Object.entries(e))s[c]={state:u,version:n.get(c)??0};return s},onSnapshot:e=>{for(let[s,c]of Object.entries(e))d({storeKey:s,state:c.state,version:c.version});}}):null;setTimeout(()=>p?.hello(),0);let l=async e=>{if(y&&i)return await y.dispatch({...e,origin:a,baseVersion:n.get(e.storeKey)??0});let s=(n.get(e.storeKey)??0)+1;return d({...e,version:s}),n.set(e.storeKey,s),p&&p.publish({...e,version:s,origin:a}),{ok:true,status:200,json:null}};return {universe:t,clientId:a,store:(e,s)=>{let c=t.getStore(e,s);return {key:e,raw:()=>c,get:()=>c.get(),subscribe:u=>c.subscribe(u),set:u=>{let b=c.get(),Z=typeof u=="function"?u(b):u;return l({storeKey:e,state:Z})}}},getStore:t.getStore.bind(t),get:(e,s)=>t.getStore(e,s??(()=>{})).get(),set:async(e,s,c)=>{let u=t.getStore(e,c??(()=>{})),b=typeof s=="function"?s(u.get()):s;return l({storeKey:e,state:b})},subscribe:(e,s,c)=>t.getStore(e,s).subscribe(c),dispatch:l,stop:()=>{y?.unsubscribe?.(),p?.stop?.();},hydrateSnapshot:v,getLastEventId:()=>g,setLastEventId:e=>{g=e;}}};function I(r){return {getSnapshot:()=>r.get(),subscribe:o=>r.subscribe(()=>o())}}exports.applyIncomingEvent=f;exports.createStore=m;exports.createUniverse=E;exports.createZuno=w;exports.startBroadcastChannel=T;exports.startSSE=h;exports.toReadable=I;//# sourceMappingURL=index.cjs.map
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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","json","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":"aAsCO,SAASA,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAKA,CACA,GAAM,CAAE,QAAA,CAAAC,CAAAA,CAAU,SAAAC,CAAS,CAAA,CAAIF,CAAAA,CAG/B,GAAID,EAAM,MAAA,GAAWE,CAAAA,CAGrB,CAAA,GAAI,OAAOF,CAAAA,CAAM,OAAA,EAAY,QAAA,CAAU,CACrC,IAAMI,CAAAA,CAAUD,CAAAA,CAAS,GAAA,CAAIH,CAAAA,CAAM,QAAQ,CAAA,EAAK,CAAA,CAChD,GAAIA,CAAAA,CAAM,SAAWI,CAAAA,CAAS,OAC9BD,CAAAA,CAAS,GAAA,CAAIH,CAAAA,CAAM,QAAA,CAAUA,CAAAA,CAAM,OAAO,EAC5C,CAGAD,CAAAA,CAAS,QAAA,CAASC,CAAAA,CAAM,SAAU,IAAMA,CAAAA,CAAM,KAAK,CAAA,CAAE,IAAIA,CAAAA,CAAM,KAAK,EAAA,CACtE,CAgBO,SAASK,CAAAA,CAASC,CAAAA,CAAiC,CACxD,GAAM,CAAE,GAAA,CAAAC,CAAAA,CAAK,OAAA,CAAAC,EAAS,QAAA,CAAAT,CAAAA,CAAU,QAAA,CAAAG,CAAAA,CAAU,SAAAC,CAAAA,CAAU,cAAA,CAAAM,CAAe,CAAA,CAAIH,CAAAA,CACnEI,CAAAA,CAAyB,IAAA,CACzBC,CAAAA,CAAa,EAEjB,SAASC,CAAAA,EAAU,CACjB,IAAMC,EAASJ,CAAAA,EAAe,CACxBK,CAAAA,CAAa,IAAI,IAAIP,CAAAA,CAAK,UAAA,CAAW,QAAA,EAAU,IAAI,CAAA,CACrDM,CAAAA,CAAS,CAAA,EAAGC,CAAAA,CAAW,aAAa,GAAA,CAAI,aAAA,CAAe,MAAA,CAAOD,CAAM,CAAC,CAAA,CAEzEH,CAAAA,CAAK,IAAI,WAAA,CAAYI,EAAW,QAAA,EAAU,CAAA,CAE1CJ,CAAAA,CAAG,gBAAA,CAAiB,UAAA,CAAaK,CAAAA,EAAW,CAC1C,GAAI,CACF,IAAMC,CAAAA,CAAO,IAAA,CAAK,MAAMD,CAAAA,CAAE,IAAI,CAAA,CAC9B,IAAA,GAAW,CAACE,CAAAA,CAAKC,CAAG,CAAA,GAAK,MAAA,CAAO,OAAA,CAAQF,CAAI,CAAA,CAAG,CAC7C,IAAMG,CAAAA,CAAID,CAAAA,CACVf,CAAAA,CAAS,GAAA,CAAIc,EAAK,IAAA,CAAK,GAAA,CAAId,CAAAA,CAAS,GAAA,CAAIc,CAAG,CAAA,EAAK,CAAA,CAAGE,CAAAA,CAAE,OAAO,CAAC,CAAA,CAC7DpB,CAAAA,CAAS,QAAA,CAASkB,EAAK,IAAME,CAAAA,CAAE,KAAK,CAAA,CAAE,IAAIA,CAAAA,CAAE,KAAK,EACnD,CACF,OAASC,CAAAA,CAAK,CACZ,OAAA,CAAQ,KAAA,CAAM,iCAAA,CAAmCA,CAAG,EACtD,CACF,CAAC,CAAA,CAEDV,CAAAA,CAAG,gBAAA,CAAiB,OAAA,CAAUK,GAAW,CACvC,GAAI,CACF,IAAMf,EAAQ,IAAA,CAAK,KAAA,CAAMe,CAAAA,CAAE,IAAI,CAAA,CAC/B,GAAIf,CAAAA,CAAM,MAAA,GAAWE,EAAU,OAE/B,GAAI,OAAOF,CAAAA,CAAM,SAAY,QAAA,CAAU,CACrC,IAAMI,CAAAA,CAAUD,EAAS,GAAA,CAAIH,CAAAA,CAAM,QAAQ,CAAA,EAAK,CAAA,CAChD,GAAIA,CAAAA,CAAM,OAAA,EAAWI,EAAS,OAC9BD,CAAAA,CAAS,GAAA,CAAIH,CAAAA,CAAM,SAAUA,CAAAA,CAAM,OAAO,EAC5C,CAEAD,EAAS,QAAA,CAASC,CAAAA,CAAM,QAAA,CAAU,IAAMA,CAAAA,CAAM,KAAK,CAAA,CAAE,GAAA,CAAIA,EAAM,KAAK,EACtE,CAAA,MAASoB,CAAAA,CAAK,CACZ,OAAA,CAAQ,KAAA,CAAM,kCAAA,CAAoCA,CAAG,EACvD,CACF,CAAC,CAAA,CAEDV,CAAAA,CAAG,MAAA,CAAS,IAAM,CAChBC,CAAAA,CAAa,EACbL,CAAAA,CAAK,MAAA,KACP,CAAA,CAEAI,EAAG,OAAA,CAAU,IAAM,CACjBA,CAAAA,EAAI,OAAM,CACVJ,CAAAA,CAAK,OAAA,IAAU,CACf,IAAMe,CAAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,IAAO,IAAA,CAAK,GAAA,CAAI,CAAA,CAAGV,CAAU,EAAG,GAAK,CAAA,CAC5DA,CAAAA,EAAAA,CACA,UAAA,CAAWC,EAASS,CAAK,EAC3B,EACF,CAEA,OAAAT,CAAAA,EAAQ,CAED,CACL,SAAU,MAAOZ,CAAAA,EAAU,CACzB,GAAI,CACEM,CAAAA,CAAK,UAAA,EACPP,CAAAA,CAAS,QAAA,CAASC,EAAM,QAAA,CAAU,IAAMA,CAAAA,CAAM,KAAK,CAAA,CAAE,GAAA,CAAIA,CAAAA,CAAM,KAAK,EAGtE,IAAMsB,CAAAA,CAAM,MAAM,KAAA,CAAMd,EAAS,CAC/B,MAAA,CAAQ,MAAA,CACR,OAAA,CAAS,CAAE,cAAA,CAAgB,kBAAmB,CAAA,CAC9C,IAAA,CAAM,IAAA,CAAK,SAAA,CAAUR,CAAK,CAC5B,CAAC,CAAA,CAED,GAAIsB,CAAAA,CAAI,MAAA,GAAW,IAAK,CACtB,IAAMC,CAAAA,CAAO,MAAMD,EAAI,IAAA,EAAK,CAC5B,GAAIC,CAAAA,CAAK,OAAA,CAAS,CAChB,GAAM,CAAE,MAAAC,CAAAA,CAAO,OAAA,CAAAC,CAAQ,CAAA,CAAIF,EAAK,OAAA,CAChCpB,CAAAA,CAAS,GAAA,CAAIH,CAAAA,CAAM,SAAUyB,CAAO,CAAA,CACpC1B,CAAAA,CAAS,QAAA,CAASC,CAAAA,CAAM,QAAA,CAAU,IAAMwB,CAAK,EAAE,GAAA,CAAIA,CAAK,EAC1D,CACA,OAAO,CAAE,EAAA,CAAI,CAAA,CAAA,CAAO,MAAA,CAAQ,IAAK,IAAA,CAAMD,CAAAA,CAAM,MAAA,CAAQ,UAAW,CAClE,CAEA,GAAI,CAACD,EAAI,EAAA,CAAI,OAAO,CAAE,EAAA,CAAI,GAAO,MAAA,CAAQA,CAAAA,CAAI,MAAA,CAAQ,IAAA,CAAM,MAAMA,CAAAA,CAAI,IAAA,EAAO,CAAA,CAE5E,IAAMI,CAAAA,CAAO,MAAMJ,CAAAA,CAAI,MAAK,CAC5B,GAAII,CAAAA,CAAK,KAAA,CAAO,CACd,GAAM,CAAE,KAAA,CAAAF,CAAAA,CAAO,QAAAC,CAAQ,CAAA,CAAIC,CAAAA,CAAK,KAAA,CAC5B,OAAOD,CAAAA,EAAY,QAAA,EACrBtB,CAAAA,CAAS,IAAIH,CAAAA,CAAM,QAAA,CAAUyB,CAAO,EAOxC,CAEA,OAAO,CAAE,EAAA,CAAI,CAAA,CAAA,CAAM,OAAQ,GAAA,CAAK,IAAA,CAAAC,CAAK,CACvC,CAAA,MAASN,CAAAA,CAAK,CACZ,OAAO,CAAE,EAAA,CAAI,KAAA,CAAO,MAAA,CAAQ,GAAA,CAAK,KAAMA,CAAAA,CAAK,MAAA,CAAQ,eAAgB,CACtE,CACF,CAAA,CACA,WAAA,CAAa,IAAM,CACjBV,CAAAA,EAAI,KAAA,GACN,CACF,CACF,CAYO,SAASiB,CAAAA,CAAsBrB,CAAAA,CAAiB,CACrD,GAAM,CAAE,WAAA,CAAAsB,CAAAA,CAAa,SAAA1B,CAAAA,CAAU,OAAA,CAAA2B,CAAAA,CAAS,WAAA,CAAAC,CAAAA,CAAa,UAAA,CAAAC,CAAW,CAAA,CAAIzB,EAC9D0B,CAAAA,CAAU,IAAI,gBAAA,CAAiBJ,CAAW,EAEhD,OAAAI,CAAAA,CAAQ,SAAA,CAAajB,CAAAA,EAAM,CACzB,IAAMkB,CAAAA,CAAMlB,CAAAA,CAAE,IAAA,CACVkB,CAAAA,CAAI,MAAA,GAAW/B,CAAAA,GAEf+B,CAAAA,CAAI,OAAS,OAAA,EAASJ,CAAAA,CAAQI,CAAAA,CAAI,KAAK,EACvCA,CAAAA,CAAI,IAAA,GAAS,OAAA,EAASD,CAAAA,CAAQ,YAAY,CAAE,IAAA,CAAM,UAAA,CAAY,QAAA,CAAUF,CAAAA,EAAY,CAAG,MAAA,CAAQ5B,CAAS,CAAC,CAAA,CACzG+B,CAAAA,CAAI,IAAA,GAAS,UAAA,EAAYF,EAAWE,CAAAA,CAAI,QAAQ,CAAA,EACtD,CAAA,CAEO,CACL,OAAA,CAAUjC,CAAAA,EAA0BgC,CAAAA,CAAQ,WAAA,CAAY,CAAE,IAAA,CAAM,OAAA,CAAS,KAAA,CAAAhC,EAAO,MAAA,CAAQE,CAAS,CAAC,CAAA,CAClG,MAAO,IAAM8B,CAAAA,CAAQ,WAAA,CAAY,CAAE,KAAM,OAAA,CAAS,MAAA,CAAQ9B,CAAS,CAAC,CAAA,CACpE,IAAA,CAAM,IAAM8B,CAAAA,CAAQ,OACtB,CACF,CC/IO,IAAME,EAAkBC,CAAAA,EAAyB,CACtD,IAAIX,CAAAA,CAAQW,EACNC,CAAAA,CAAY,IAAI,GAAA,CAEtB,OAAO,CACL,GAAA,CAAK,IAAMZ,CAAAA,CACX,IAAMa,CAAAA,EAAS,CACb,IAAMC,CAAAA,CAAQ,OAAOD,CAAAA,EAAS,UAAA,CAAcA,CAAAA,CAAwBb,CAAK,EAAIa,CAAAA,CACzE,MAAA,CAAO,EAAA,CAAGC,CAAAA,CAAOd,CAAK,CAAA,GAC1BA,CAAAA,CAAQc,CAAAA,CACRF,EAAU,OAAA,CAASG,CAAAA,EAAMA,CAAAA,CAAEf,CAAK,CAAC,CAAA,EACnC,CAAA,CACA,SAAA,CAAYgB,CAAAA,GACVJ,EAAU,GAAA,CAAII,CAAQ,CAAA,CACf,IAAMJ,CAAAA,CAAU,MAAA,CAAOI,CAAQ,CAAA,CAE1C,CACF,CAAA,CAOaC,CAAAA,CAAiB,IAAgB,CAC5C,IAAMC,CAAAA,CAAS,IAAI,GAAA,CAyCnB,OAvC2B,CACzB,QAAA,CAAYzB,CAAAA,CAAa0B,CAAAA,CAAyB,CAChD,OAAKD,CAAAA,CAAO,GAAA,CAAIzB,CAAG,GACjByB,CAAAA,CAAO,GAAA,CAAIzB,CAAAA,CAAKiB,CAAAA,CAAYS,GAAM,CAAC,CAAA,CAE9BD,CAAAA,CAAO,IAAIzB,CAAG,CACvB,CAAA,CACA,QAAA,EAAoC,CAClC,IAAM2B,CAAAA,CAA+B,GACrC,IAAA,GAAW,CAAC3B,CAAAA,CAAK4B,CAAK,IAAKH,CAAAA,CAAO,OAAA,EAAQ,CACxCE,CAAAA,CAAI3B,CAAG,CAAA,CAAI4B,CAAAA,CAAM,GAAA,EAAI,CAEvB,OAAOD,CACT,CAAA,CACA,OAAA,CAAQrB,EAAqC,CAC3C,IAAA,GAAW,CAACN,CAAAA,CAAKqB,CAAK,CAAA,GAAK,MAAA,CAAO,OAAA,CAAQf,CAAI,EAAG,CAC/C,IAAMuB,CAAAA,CAAWJ,CAAAA,CAAO,GAAA,CAAIzB,CAAG,CAAA,CAC3B6B,CAAAA,CACFA,EAAS,GAAA,CAAIR,CAAY,CAAA,CAEzBI,CAAAA,CAAO,IAAIzB,CAAAA,CAAKiB,CAAAA,CAAYI,CAAY,CAAC,EAE7C,CACF,CAAA,CACA,MAAA,CAAOrB,CAAAA,CAAmB,CACxByB,CAAAA,CAAO,MAAA,CAAOzB,CAAG,EACnB,CAAA,CACA,KAAA,EAAc,CACZyB,CAAAA,CAAO,QACT,CAAA,CACA,eAAA,CAAgBK,CAAAA,CAAwB,CACtC,IAAMC,CAAAA,CAAiC,EAAC,CACxC,IAAA,GAAW,CAACC,CAAAA,CAAG/B,CAAG,IAAK,MAAA,CAAO,OAAA,CAAQ6B,CAAAA,CAAS,KAAK,EAClDC,CAAAA,CAAMC,CAAC,CAAA,CAAI/B,CAAAA,CAAI,MAEjB,IAAA,CAAK,OAAA,CAAQ8B,CAAK,EACpB,CACF,CAGF,CAAA,CAOaE,CAAAA,CAAa,CAAC5C,CAAAA,CAA0B,EAAC,GAAM,KAEpDH,CAAAA,CAAW,IAAI,GAAA,CACfJ,CAAAA,CAAWO,CAAAA,CAAK,QAAA,EAAYmC,CAAAA,EAAe,CAC3CvC,CAAAA,CAAWI,CAAAA,CAAK,UAAa,UAAA,CAAW,MAAA,EAAQ,UAAA,IAAa,EAAK,OAAO,IAAA,CAAK,MAAA,EAAQ,CAAA,CACxF6C,EAAW,KAAA,CACXC,CAAAA,CAAc,EAElB,SAASC,CAAAA,CAAgBN,CAAAA,CAAwB,CAC/C,IAAMC,EAAiC,EAAC,CACxC,IAAA,GAAW,CAACC,EAAG/B,CAAG,CAAA,GAAK,MAAA,CAAO,OAAA,CAAQ6B,EAAS,KAAK,CAAA,CAClDC,CAAAA,CAAMC,CAAC,CAAA,CAAI/B,CAAAA,CAAI,KAAA,CACff,CAAAA,CAAS,IAAI8C,CAAAA,CAAG/B,CAAAA,CAAI,OAAO,CAAA,CAE7BnB,EAAS,OAAA,CAAQiD,CAAK,CAAA,CACtBI,CAAAA,CAAcL,EAAS,YACzB,CAEA,IAAMO,CAAAA,CAAStD,CAAAA,EAA0B,CACnC,OAAOA,CAAAA,CAAM,SAAY,QAAA,GAC3BoD,CAAAA,CAAc,IAAA,CAAK,GAAA,CAAIA,EAAapD,CAAAA,CAAM,OAAO,CAAA,CAAA,CAEnDF,CAAAA,CAAmBC,EAAUC,CAAAA,CAAO,CAAE,QAAA,CAAAE,CAAAA,CAAsB,QAAA,CAAAC,CAAS,CAAC,EACxE,CAAA,CAEMoD,CAAAA,CAAMjD,CAAAA,CAAK,QAAUA,CAAAA,CAAK,OAAA,CAC5BD,CAAAA,CAAS,CACP,SAAAN,CAAAA,CACA,GAAA,CAAKO,CAAAA,CAAK,MAAA,CACV,OAAA,CAASA,CAAAA,CAAK,OAAA,CACd,UAAA,CAAYA,EAAK,UAAA,EAAc,IAAA,CAC/B,QAAA,CAAAJ,CAAAA,CACA,SAAAC,CAAAA,CACA,cAAA,CAAgB,IAAMiD,CAAAA,CACtB,OAAQ,IAAM,CAAED,CAAAA,CAAW,KAAM,CAAA,CACjC,OAAA,CAAS,IAAM,CAAEA,EAAW,MAAO,CACrC,CAAC,CAAA,CACD,KAEEK,CAAAA,CAAKlD,CAAAA,CAAK,WAAA,CACZqB,CAAAA,CAAsB,CACpB,WAAA,CAAarB,CAAAA,CAAK,WAAA,CAClB,QAAA,CAAAJ,CAAAA,CACA,OAAA,CAASoD,CAAAA,CACT,WAAA,CAAa,IAAM,CACjB,IAAMtC,CAAAA,CAAOjB,CAAAA,CAAS,UAAS,CACzB6C,CAAAA,CAA2D,EAAC,CAClE,OAAW,CAACa,CAAAA,CAAUjC,CAAK,CAAA,GAAK,MAAA,CAAO,OAAA,CAAQR,CAAI,CAAA,CACjD4B,EAAIa,CAAQ,CAAA,CAAI,CAAE,KAAA,CAAAjC,EAAO,OAAA,CAASrB,CAAAA,CAAS,GAAA,CAAIsD,CAAQ,GAAK,CAAE,CAAA,CAEhE,OAAOb,CACT,CAAA,CACA,UAAA,CAAa5B,CAAAA,EAAS,CACpB,OAAW,CAACyC,CAAAA,CAAUvC,CAAG,CAAA,GAAK,OAAO,OAAA,CAAQF,CAAI,CAAA,CAC/CsC,CAAAA,CAAM,CAAE,QAAA,CAAAG,CAAAA,CAAU,KAAA,CAAOvC,CAAAA,CAAI,KAAA,CAAO,OAAA,CAASA,CAAAA,CAAI,OAAQ,CAAC,EAE9D,CACF,CAAC,CAAA,CACD,KAEJ,UAAA,CAAW,IAAMsC,CAAAA,EAAI,KAAA,GAAS,CAAC,CAAA,CAE/B,IAAME,CAAAA,CAAW,MAAO1D,CAAAA,EAA0B,CAChD,GAAIuD,GAAOJ,CAAAA,CACT,OAAO,MAAMI,CAAAA,CAAI,SAAS,CACxB,GAAGvD,CAAAA,CACH,MAAA,CAAQE,EACR,WAAA,CAAaC,CAAAA,CAAS,GAAA,CAAIH,CAAAA,CAAM,QAAQ,CAAA,EAAK,CAC/C,CAAC,EAGH,IAAM2D,CAAAA,CAAAA,CAAexD,CAAAA,CAAS,GAAA,CAAIH,EAAM,QAAQ,CAAA,EAAK,CAAA,EAAK,CAAA,CAC1D,OAAAsD,CAAAA,CAAM,CAAE,GAAGtD,CAAAA,CAAO,OAAA,CAAS2D,CAAY,CAAC,CAAA,CACxCxD,EAAS,GAAA,CAAIH,CAAAA,CAAM,QAAA,CAAU2D,CAAW,EAEpCH,CAAAA,EACFA,CAAAA,CAAG,OAAA,CAAQ,CAAE,GAAGxD,CAAAA,CAAO,OAAA,CAAS2D,CAAAA,CAAa,MAAA,CAAQzD,CAAS,CAAC,CAAA,CAG1D,CAAE,GAAI,IAAA,CAAM,MAAA,CAAQ,GAAA,CAAK,IAAA,CAAM,IAAK,CAC7C,CAAA,CAiBA,OAAO,CACL,SAAAH,CAAAA,CACA,QAAA,CAAAG,CAAAA,CACA,KAAA,CAlBY,CAAKuD,CAAAA,CAAkBd,CAAAA,GAAiC,CACpE,IAAMiB,CAAAA,CAAW7D,CAAAA,CAAS,QAAA,CAAY0D,CAAAA,CAAUd,CAAI,CAAA,CACpD,OAAO,CACL,GAAA,CAAKc,EACL,GAAA,CAAK,IAAMG,CAAAA,CACX,GAAA,CAAK,IAAMA,CAAAA,CAAS,GAAA,EAAI,CACxB,UAAYC,CAAAA,EAAOD,CAAAA,CAAS,SAAA,CAAUC,CAAE,EACxC,GAAA,CAAMxB,CAAAA,EAAS,CACb,IAAMyB,EAAOF,CAAAA,CAAS,GAAA,EAAI,CACpBpC,CAAAA,CAAQ,OAAOa,CAAAA,EAAS,UAAA,CAAcA,CAAAA,CAAwByB,CAAI,CAAA,CAAIzB,CAAAA,CAC5E,OAAOqB,CAAAA,CAAS,CAAE,QAAA,CAAAD,CAAAA,CAAU,KAAA,CAAAjC,CAAM,CAAC,CACrC,CACF,CACF,CAAA,CAME,QAAA,CAAUzB,CAAAA,CAAS,QAAA,CAAS,IAAA,CAAKA,CAAQ,CAAA,CACzC,GAAA,CAAK,CAAKkB,CAAAA,CAAa0B,IAAmB5C,CAAAA,CAAS,QAAA,CAAYkB,CAAAA,CAAK0B,CAAAA,GAAS,IAAG,CAAA,CAAA,CAAoB,CAAA,CAAE,GAAA,EAAI,CAC1G,GAAA,CAAK,MAAW1B,CAAAA,CAAaoB,CAAAA,CAA4BM,IAAmB,CAC1E,IAAMoB,CAAAA,CAAIhE,CAAAA,CAAS,SAAYkB,CAAAA,CAAK0B,CAAAA,GAAS,IAAG,CAAA,CAAA,CAAoB,EAC9DnB,CAAAA,CAAQ,OAAOa,CAAAA,EAAS,UAAA,CAAcA,CAAAA,CAAwB0B,CAAAA,CAAE,GAAA,EAAK,EAAI1B,CAAAA,CAC/E,OAAOqB,CAAAA,CAAS,CAAE,SAAUzC,CAAAA,CAAK,KAAA,CAAAO,CAAM,CAAC,CAC1C,CAAA,CACA,SAAA,CAAW,CAAKP,CAAAA,CAAa0B,CAAAA,CAAekB,CAAAA,GAA2B9D,CAAAA,CAAS,QAAA,CAAYkB,EAAK0B,CAAI,CAAA,CAAE,SAAA,CAAUkB,CAAE,EACnH,QAAA,CAAAH,CAAAA,CACA,IAAA,CAAM,IAAM,CACVH,CAAAA,EAAK,WAAA,IAAc,CACnBC,CAAAA,EAAI,IAAA,KACN,CAAA,CACA,eAAA,CAAAH,EACA,cAAA,CAAgB,IAAMD,CAAAA,CACtB,cAAA,CAAiBY,GAAe,CAAEZ,CAAAA,CAAcY,EAAI,CACtD,CACF,EC9OO,SAASC,CAAAA,CAAcpB,CAAAA,CAAkD,CAC9E,OAAO,CACL,WAAA,CAAa,IAAMA,EAAM,GAAA,EAAI,CAC7B,SAAA,CAAYqB,CAAAA,EAAarB,EAAM,SAAA,CAAU,IAAMqB,CAAAA,EAAU,CAC3D,CACF","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 if (opts.optimistic) {\n universe.getStore(event.storeKey, () => event.state).set(event.state);\n }\n\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 const json = await res.json();\n if (json.event) {\n const { state, version } = json.event;\n if (typeof version === \"number\") {\n versions.set(event.storeKey, version);\n }\n // We don't strictly need to set the store here because we already did it optimistically?\n // BUT, if the server modified it (e.g. server-side logic), we should.\n // However, for pure optimistic updates, we rely on the fact that we already set it.\n // The critical part is THE VERSION.\n // Let's just update the version to avoid the 409.\n }\n\n return { ok: true, status: 200, 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,5 +1,7 @@
|
|
|
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, e as applyIncomingEvent, b as createStore, a as createUniverse, c as createZuno, d as startBroadcastChannel, s as startSSE } from './index-IbvF6TBr.cjs';
|
|
2
|
+
|
|
1
3
|
/** Universal UI adapter contract */
|
|
2
|
-
|
|
4
|
+
type ZunoReadable<T> = {
|
|
3
5
|
/** Read current value (sync) */
|
|
4
6
|
getSnapshot(): T;
|
|
5
7
|
/**
|
|
@@ -12,10 +14,11 @@ export type ZunoReadable<T> = {
|
|
|
12
14
|
getServerSnapshot?: () => T;
|
|
13
15
|
};
|
|
14
16
|
/** Minimal store shape that can be adapted into a readable */
|
|
15
|
-
|
|
17
|
+
type ZunoSubscribableStore<T> = {
|
|
16
18
|
get(): T;
|
|
17
19
|
subscribe(cb: (state: T) => void): () => void;
|
|
18
20
|
};
|
|
19
21
|
/** Adapter helper: convert store => readable */
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
declare function toReadable<T>(store: ZunoSubscribableStore<T>): ZunoReadable<T>;
|
|
23
|
+
|
|
24
|
+
export { type ZunoReadable, type ZunoSubscribableStore, toReadable };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
export { createZuno } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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, e as applyIncomingEvent, b as createStore, a as createUniverse, c as createZuno, d as startBroadcastChannel, s as startSSE } from './index-IbvF6TBr.js';
|
|
2
|
+
|
|
3
|
+
/** Universal UI adapter contract */
|
|
4
|
+
type ZunoReadable<T> = {
|
|
5
|
+
/** Read current value (sync) */
|
|
6
|
+
getSnapshot(): T;
|
|
7
|
+
/**
|
|
8
|
+
* Subscribe to changes.
|
|
9
|
+
* Call `onChange()` whenever snapshot may have changed.
|
|
10
|
+
* Return unsubscribe.
|
|
11
|
+
*/
|
|
12
|
+
subscribe(onChange: () => void): () => void;
|
|
13
|
+
/** Optional: React SSR (server snapshot) */
|
|
14
|
+
getServerSnapshot?: () => T;
|
|
15
|
+
};
|
|
16
|
+
/** Minimal store shape that can be adapted into a readable */
|
|
17
|
+
type ZunoSubscribableStore<T> = {
|
|
18
|
+
get(): T;
|
|
19
|
+
subscribe(cb: (state: T) => void): () => void;
|
|
20
|
+
};
|
|
21
|
+
/** Adapter helper: convert store => readable */
|
|
22
|
+
declare function toReadable<T>(store: ZunoSubscribableStore<T>): ZunoReadable<T>;
|
|
23
|
+
|
|
24
|
+
export { type ZunoReadable, type ZunoSubscribableStore, toReadable };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { startSSE } from "./sync/sse-client";
|
|
4
|
-
export { startBroadcastChannel } from "./sync/broadcast-channel";
|
|
5
|
-
export * from "./sync/transport";
|
|
6
|
-
export { toReadable } from "./shared/readable";
|
|
1
|
+
function f(r,o,n){let{clientId:t,versions:a}=n;if(o.origin!==t){if(typeof o.version=="number"){let i=a.get(o.storeKey)??0;if(o.version<=i)return;a.set(o.storeKey,o.version);}r.getStore(o.storeKey,()=>o.state).set(o.state);}}function h(r){let{url:o,syncUrl:n,universe:t,clientId:a,versions:i,getLastEventId:g}=r,v=null,d=0;function y(){let p=g(),l=new URL(o,globalThis.location?.href);p>0&&l.searchParams.set("lastEventId",String(p)),v=new EventSource(l.toString()),v.addEventListener("snapshot",S=>{try{let e=JSON.parse(S.data);for(let[s,c]of Object.entries(e)){let u=c;i.set(s,Math.max(i.get(s)??0,u.version)),t.getStore(s,()=>u.state).set(u.state);}}catch(e){console.error("[Zuno] Failed to parse snapshot",e);}}),v.addEventListener("state",S=>{try{let e=JSON.parse(S.data);if(e.origin===a)return;if(typeof e.version=="number"){let s=i.get(e.storeKey)??0;if(e.version<=s)return;i.set(e.storeKey,e.version);}t.getStore(e.storeKey,()=>e.state).set(e.state);}catch(e){console.error("[Zuno] Failed to parse SSE event",e);}}),v.onopen=()=>{d=0,r.onOpen?.();},v.onerror=()=>{v?.close(),r.onClose?.();let S=Math.min(1e3*Math.pow(2,d),3e4);d++,setTimeout(y,S);};}return y(),{dispatch:async p=>{try{r.optimistic&&t.getStore(p.storeKey,()=>p.state).set(p.state);let l=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(p)});if(l.status===409){let e=await l.json();if(e.current){let{state:s,version:c}=e.current;i.set(p.storeKey,c),t.getStore(p.storeKey,()=>s).set(s);}return {ok:!1,status:409,json:e,reason:"CONFLICT"}}if(!l.ok)return {ok:!1,status:l.status,json:await l.json()};let S=await l.json();if(S.event){let{state:e,version:s}=S.event;typeof s=="number"&&i.set(p.storeKey,s);}return {ok:!0,status:200,json:S}}catch(l){return {ok:false,status:500,json:l,reason:"NETWORK_ERROR"}}},unsubscribe:()=>{v?.close();}}}function T(r){let{channelName:o,clientId:n,onEvent:t,getSnapshot:a,onSnapshot:i}=r,g=new BroadcastChannel(o);return g.onmessage=v=>{let d=v.data;d.origin!==n&&(d.type==="event"&&t(d.event),d.type==="hello"&&g.postMessage({type:"snapshot",snapshot:a(),origin:n}),d.type==="snapshot"&&i(d.snapshot));},{publish:v=>g.postMessage({type:"event",event:v,origin:n}),hello:()=>g.postMessage({type:"hello",origin:n}),stop:()=>g.close()}}var m=r=>{let o=r,n=new Set;return {get:()=>o,set:t=>{let a=typeof t=="function"?t(o):t;Object.is(a,o)||(o=a,n.forEach(i=>i(o)));},subscribe:t=>(n.add(t),()=>n.delete(t))}},E=()=>{let r=new Map;return {getStore(n,t){return r.has(n)||r.set(n,m(t())),r.get(n)},snapshot(){let n={};for(let[t,a]of r.entries())n[t]=a.get();return n},restore(n){for(let[t,a]of Object.entries(n)){let i=r.get(t);i?i.set(a):r.set(t,m(a));}},delete(n){r.delete(n);},clear(){r.clear();},hydrateSnapshot(n){let t={};for(let[a,i]of Object.entries(n.state))t[a]=i.state;this.restore(t);}}},w=(r={})=>{let n=new Map,t=r.universe??E(),a=r.clientId??globalThis.crypto?.randomUUID?.()??String(Math.random()),i=false,g=0;function v(e){let s={};for(let[c,u]of Object.entries(e.state))s[c]=u.state,n.set(c,u.version);t.restore(s),g=e.lastEventId;}let d=e=>{typeof e.eventId=="number"&&(g=Math.max(g,e.eventId)),f(t,e,{clientId:a,versions:n});},y=r.sseUrl&&r.syncUrl?h({universe:t,url:r.sseUrl,syncUrl:r.syncUrl,optimistic:r.optimistic??true,clientId:a,versions:n,getLastEventId:()=>g,onOpen:()=>{i=true;},onClose:()=>{i=false;}}):null,p=r.channelName?T({channelName:r.channelName,clientId:a,onEvent:d,getSnapshot:()=>{let e=t.snapshot(),s={};for(let[c,u]of Object.entries(e))s[c]={state:u,version:n.get(c)??0};return s},onSnapshot:e=>{for(let[s,c]of Object.entries(e))d({storeKey:s,state:c.state,version:c.version});}}):null;setTimeout(()=>p?.hello(),0);let l=async e=>{if(y&&i)return await y.dispatch({...e,origin:a,baseVersion:n.get(e.storeKey)??0});let s=(n.get(e.storeKey)??0)+1;return d({...e,version:s}),n.set(e.storeKey,s),p&&p.publish({...e,version:s,origin:a}),{ok:true,status:200,json:null}};return {universe:t,clientId:a,store:(e,s)=>{let c=t.getStore(e,s);return {key:e,raw:()=>c,get:()=>c.get(),subscribe:u=>c.subscribe(u),set:u=>{let b=c.get(),Z=typeof u=="function"?u(b):u;return l({storeKey:e,state:Z})}}},getStore:t.getStore.bind(t),get:(e,s)=>t.getStore(e,s??(()=>{})).get(),set:async(e,s,c)=>{let u=t.getStore(e,c??(()=>{})),b=typeof s=="function"?s(u.get()):s;return l({storeKey:e,state:b})},subscribe:(e,s,c)=>t.getStore(e,s).subscribe(c),dispatch:l,stop:()=>{y?.unsubscribe?.(),p?.stop?.();},hydrateSnapshot:v,getLastEventId:()=>g,setLastEventId:e=>{g=e;}}};function I(r){return {getSnapshot:()=>r.get(),subscribe:o=>r.subscribe(()=>o())}}export{f as applyIncomingEvent,m as createStore,E as createUniverse,w as createZuno,T as startBroadcastChannel,h as startSSE,I as toReadable};//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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","json","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 if (opts.optimistic) {\n universe.getStore(event.storeKey, () => event.state).set(event.state);\n }\n\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 const json = await res.json();\n if (json.event) {\n const { state, version } = json.event;\n if (typeof version === \"number\") {\n versions.set(event.storeKey, version);\n }\n // We don't strictly need to set the store here because we already did it optimistically?\n // BUT, if the server modified it (e.g. server-side logic), we should.\n // However, for pure optimistic updates, we rely on the fact that we already set it.\n // The critical part is THE VERSION.\n // Let's just update the version to avoid the 409.\n }\n\n return { ok: true, status: 200, 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"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';var p=new Map,f=t=>p.get(t),d=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,x=1,a=[],S=t=>(t.eventId=x++,a.push(t),a.length>h&&a.shift(),t),y=t=>a.filter(e=>(e?.eventId??0)>t),g=()=>a[a.length-1]?.eventId??0,v=new Set,l=t=>(v.add(t),()=>{v.delete(t);}),m=t=>{v.forEach(e=>e(t));};function E(t){let e=f(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 d(n),S(n),m(n),{ok:true,event:n}}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=y(r);for(let u of i)e.write(`id: ${u.eventId}
|
|
2
|
+
`),e.write(`event: state
|
|
3
|
+
`),e.write(`data: ${JSON.stringify(u)}
|
|
4
|
+
|
|
5
|
+
`);}else e.write(`event: snapshot
|
|
6
|
+
`),e.write(`data: ${JSON.stringify(c())}
|
|
7
|
+
|
|
8
|
+
`);let s=l(i=>{e.write(`id: ${i.eventId}
|
|
9
|
+
`),e.write(`event: state
|
|
10
|
+
`),e.write(`data: ${JSON.stringify(i)}
|
|
11
|
+
|
|
12
|
+
`);}),I=setInterval(()=>{e.write(`: ping ${Date.now()}
|
|
13
|
+
|
|
14
|
+
`);},15e3);e.write(`: connected
|
|
15
|
+
|
|
16
|
+
`),t.on("close",()=>{clearInterval(I),s(),e.end();});},O=(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=E(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)=>O(t,e);function k(t,e){let o={state:c(),lastEventId:g()};"json"in e&&typeof e.json=="function"?e.json(o):(e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify(o)));}exports.appendEvent=S;exports.applyStateEvent=E;exports.createSSEConnection=A;exports.getEventsAfter=y;exports.getLastEventId=g;exports.getUniverseRecord=f;exports.getUniverseState=c;exports.publishToStateEvent=m;exports.sendSnapshot=k;exports.setUniverseState=L;exports.subscribeToStateEvents=l;exports.syncUniverseState=O;exports.updateUniverseState=d;//# sourceMappingURL=index.cjs.map
|
|
17
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|