@iadev93/zuno 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +195 -0
  2. package/dist/core/createZuno.d.ts +77 -0
  3. package/dist/core/createZuno.d.ts.map +1 -0
  4. package/dist/core/createZuno.js +250 -0
  5. package/dist/core/store.d.ts +10 -0
  6. package/dist/core/store.d.ts.map +1 -0
  7. package/dist/core/store.js +27 -0
  8. package/dist/core/types.d.ts +107 -0
  9. package/dist/core/types.d.ts.map +1 -0
  10. package/dist/core/types.js +1 -0
  11. package/dist/core/universe.d.ts +12 -0
  12. package/dist/core/universe.d.ts.map +1 -0
  13. package/dist/core/universe.js +53 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +6 -0
  17. package/dist/server/apply-state-event.d.ts +26 -0
  18. package/dist/server/apply-state-event.d.ts.map +1 -0
  19. package/dist/server/apply-state-event.js +29 -0
  20. package/dist/server/index.d.ts +4 -0
  21. package/dist/server/index.d.ts.map +1 -0
  22. package/dist/server/index.js +3 -0
  23. package/dist/server/server-transport.d.ts +8 -0
  24. package/dist/server/server-transport.d.ts.map +1 -0
  25. package/dist/server/server-transport.js +25 -0
  26. package/dist/server/snapshot-handler.d.ts +9 -0
  27. package/dist/server/snapshot-handler.d.ts.map +1 -0
  28. package/dist/server/snapshot-handler.js +18 -0
  29. package/dist/server/sse-handler.d.ts +24 -0
  30. package/dist/server/sse-handler.d.ts.map +1 -0
  31. package/dist/server/sse-handler.js +127 -0
  32. package/dist/server/state.bus.d.ts +18 -0
  33. package/dist/server/state.bus.d.ts.map +1 -0
  34. package/dist/server/state.bus.js +26 -0
  35. package/dist/server/state.log.d.ts +22 -0
  36. package/dist/server/state.log.d.ts.map +1 -0
  37. package/dist/server/state.log.js +37 -0
  38. package/dist/server/types.d.ts +8 -0
  39. package/dist/server/types.d.ts.map +1 -0
  40. package/dist/server/types.js +1 -0
  41. package/dist/server/universe-store.d.ts +29 -0
  42. package/dist/server/universe-store.d.ts.map +1 -0
  43. package/dist/server/universe-store.js +26 -0
  44. package/dist/shared/readable.d.ts +21 -0
  45. package/dist/shared/readable.d.ts.map +1 -0
  46. package/dist/shared/readable.js +7 -0
  47. package/dist/sync/apply-incoming-event.d.ts +10 -0
  48. package/dist/sync/apply-incoming-event.d.ts.map +1 -0
  49. package/dist/sync/apply-incoming-event.js +28 -0
  50. package/dist/sync/broadcast-channel.d.ts +12 -0
  51. package/dist/sync/broadcast-channel.d.ts.map +1 -0
  52. package/dist/sync/broadcast-channel.js +73 -0
  53. package/dist/sync/sse-client.d.ts +21 -0
  54. package/dist/sync/sse-client.d.ts.map +1 -0
  55. package/dist/sync/sse-client.js +162 -0
  56. package/dist/sync/sync-types.d.ts +164 -0
  57. package/dist/sync/sync-types.d.ts.map +1 -0
  58. package/dist/sync/sync-types.js +1 -0
  59. package/dist/sync/transport.d.ts +10 -0
  60. package/dist/sync/transport.d.ts.map +1 -0
  61. package/dist/sync/transport.js +26 -0
  62. package/package.json +25 -0
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # @iadev93/zuno
2
+
3
+ **Zuno** is a universal, event-driven state system designed to keep **client, server, and multiple runtimes** in sync with strong consistency guarantees.
4
+
5
+ Zuno is built around a simple idea:
6
+
7
+ > State is not local — it is **distributed, versioned, and observable**.
8
+
9
+ This package is the **core** of the Zuno ecosystem:
10
+
11
+ * State engine (Universe + Stores)
12
+ * Versioned event model
13
+ * Sync primitives (SSE + BroadcastChannel)
14
+ * Adapter contract (`ZunoReadable`) for UI/framework bindings
15
+ * Optional server helpers (snapshot + SSE handlers)
16
+
17
+ ---
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @iadev93/zuno
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Quick Start (Client)
28
+
29
+ ```ts
30
+ import { createZuno } from "@iadev93/zuno";
31
+
32
+ const zuno = createZuno();
33
+
34
+ const counter = zuno.store("counter", () => 0);
35
+
36
+ await counter.set((v) => v + 1);
37
+ console.log(counter.get());
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Core Concepts
43
+
44
+ ### Universe
45
+
46
+ A Universe is a container for many stores. It is responsible for:
47
+
48
+ * creating and caching stores by key
49
+ * coordinating versioned state events
50
+ * providing a stable API for sync/transports
51
+
52
+ ### Store
53
+
54
+ A Store is keyed state.
55
+
56
+ * `get()` returns current snapshot
57
+ * `set(next)` updates state (supports functional updates)
58
+ * `subscribe(cb)` notifies on changes
59
+
60
+ ### State Events (Versioned)
61
+
62
+ Zuno sync is driven by **versioned state events**. Each event includes:
63
+
64
+ * `storeKey`
65
+ * `state`
66
+ * `origin` (who produced it)
67
+ * `baseVersion` (what it was based on)
68
+ * `version` (monotonic)
69
+ * `eventId` (optional)
70
+
71
+ This enables deterministic ordering and protects against stale overwrites.
72
+
73
+ ---
74
+
75
+ ## Client Sync
76
+
77
+ Zuno supports multi-tab / multi-client synchronization.
78
+
79
+ ### 1) Same-origin tabs (BroadcastChannel)
80
+
81
+ ```ts
82
+ import { startBroadcastChannel } from "@iadev93/zuno";
83
+
84
+ startBroadcastChannel({
85
+ channelName: "zuno-demo"
86
+ });
87
+ ```
88
+
89
+ > BroadcastChannel works **only across the same origin**.
90
+
91
+ ### 2) Multi-client sync (SSE)
92
+
93
+ ```ts
94
+ import { startSSE } from "@iadev93/zuno";
95
+
96
+ startSSE({
97
+ url: "/zuno/events",
98
+ // optional: pass shared Maps for version bookkeeping
99
+ // versions,
100
+ });
101
+ ```
102
+
103
+ SSE is ideal for:
104
+
105
+ * low-latency state fanout
106
+ * CDN/proxy friendly infra
107
+ * avoiding WebSocket lock-in
108
+
109
+ ---
110
+
111
+ ## Adapter Contract (UI / Frameworks)
112
+
113
+ Zuno exposes a minimal adapter contract that can be consumed by any UI/runtime:
114
+
115
+ ```ts
116
+ type ZunoReadable<T> = {
117
+ getSnapshot(): T;
118
+ subscribe(onChange: () => void): () => void;
119
+ getServerSnapshot?: () => T;
120
+ };
121
+ ```
122
+
123
+ Helper:
124
+
125
+ ```ts
126
+ import { toReadable } from "@iadev93/zuno";
127
+
128
+ const readable = toReadable(store);
129
+ ```
130
+
131
+ This contract is used by `@iadev93/zuno-react` and future adapters (Solid/Vue/Svelte/etc.).
132
+
133
+ ---
134
+
135
+ ## Server Usage (Optional Helpers)
136
+
137
+ If you want to host Zuno sync endpoints yourself (without `@iadev93/zuno-express`), the core package provides server-side utilities via the `@iadev93/zuno/server` entry point.
138
+
139
+ ### Snapshot handler
140
+
141
+ The snapshot handler returns the current universe/store snapshot for new clients.
142
+
143
+ ```ts
144
+ import { /* snapshot handler export */ } from "@iadev93/zuno/server";
145
+ ```
146
+
147
+ ### SSE connection + state publishing
148
+
149
+ Zuno’s SSE utilities typically do two jobs:
150
+
151
+ * register a client connection
152
+ * broadcast state events to connected clients
153
+
154
+ ```ts
155
+ import { createSSEConnection, setUniverseState } from "@iadev93/zuno/server";
156
+ ```
157
+
158
+ ### Applying incoming events
159
+
160
+ Incoming events should be validated and applied using the core apply routine.
161
+
162
+ ```ts
163
+ import { /* apply-state-event export */ } from "@iadev93/zuno/server";
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Recommended: Express Integration
169
+
170
+ If you’re using Express, use the dedicated adapter:
171
+
172
+ ```bash
173
+ npm install @iadev93/zuno-express
174
+ ```
175
+
176
+ It wires SSE + snapshot routes cleanly and keeps your core imports tidy.
177
+
178
+ ---
179
+
180
+ ## Public Exports
181
+
182
+ Core exports are intentionally minimal:
183
+
184
+ * `createZuno`, `CreateZunoOptions`
185
+ * `startSSE`, `startBroadcastChannel`
186
+ * `ZunoReadable`, `ZunoSubscribableStore`, `toReadable`
187
+ * (optional) server helpers if you choose to expose them
188
+
189
+ If you export server helpers from core, consider **server-only subpath exports** to prevent accidental client bundling.
190
+
191
+ ---
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,77 @@
1
+ import type { ZunoStateEvent } from "../sync/sync-types";
2
+ import type { Universe, ZunoSnapshot } from "./types";
3
+ /** Store */
4
+ type ZunoStore<T> = {
5
+ get(): T;
6
+ set(next: T): void;
7
+ subscribe(cb: (state: T) => void): () => void;
8
+ };
9
+ export type CreateZunoOptions = {
10
+ /** Universe */
11
+ universe?: Universe;
12
+ /** Optional transports */
13
+ channelName?: string;
14
+ /** SSE */
15
+ sseUrl?: string;
16
+ /** Sync */
17
+ syncUrl?: string;
18
+ /** Behavior */
19
+ optimistic?: boolean;
20
+ /** Client ID */
21
+ clientId?: string;
22
+ };
23
+ /**
24
+ * Creates a Zuno instance, which provides a state management system with optional synchronization
25
+ * capabilities via Server-Sent Events (SSE) and BroadcastChannel.
26
+ *
27
+ * @param opts - Configuration options for the Zuno instance.
28
+ * @param opts.universe - An optional pre-existing ZunoUniverse instance. If not provided, a new one will be created.
29
+ * @param opts.channelName - An optional name for the BroadcastChannel to enable local tab synchronization.
30
+ * @param opts.sseUrl - The URL for the Server-Sent Events endpoint to receive state updates from a server.
31
+ * @param opts.syncUrl - The URL for the synchronization endpoint to send state updates to a server. Required if `sseUrl` is provided.
32
+ * @param opts.optimistic - A boolean indicating whether state updates should be applied optimistically before server confirmation. Defaults to `true`.
33
+ * @param opts.clientId - A unique identifier for the client. If not provided, a random UUID will be generated.
34
+ * @returns An object containing methods to interact with the Zuno instance, including `getStore`, `destroy`, and `broadcast`.
35
+ */
36
+ export declare const createZuno: (opts?: CreateZunoOptions) => {
37
+ /** Universe */
38
+ universe: Universe;
39
+ /** Client ID */
40
+ clientId: string;
41
+ /** Get store */
42
+ getStore: <T>(storeKey: string, init: () => T) => import("./types").Store<T>;
43
+ /** Create store */
44
+ store: <T>(storeKey: string, init: () => T) => {
45
+ key: string;
46
+ get: () => T;
47
+ set: (next: T | ((prev: T) => T)) => Promise<any>;
48
+ subscribe: (cb: (state: T) => void) => () => void;
49
+ raw: () => ZunoStore<T>;
50
+ };
51
+ /** Get state */
52
+ get: <T>(storeKey: string, init?: () => T) => T;
53
+ /** Set state */
54
+ set: <T>(storeKey: string, next: T | ((prev: T) => T), init?: () => T) => Promise<{
55
+ ok: boolean;
56
+ status: number;
57
+ json: any;
58
+ }>;
59
+ /** Subscribe to store */
60
+ subscribe: <T>(storeKey: string, init: () => T, cb: (state: T) => void) => () => boolean;
61
+ /** Dispatch event */
62
+ dispatch: (event: ZunoStateEvent) => Promise<{
63
+ ok: boolean;
64
+ status: number;
65
+ json: any;
66
+ }>;
67
+ /** Stop */
68
+ stop: () => void;
69
+ /** Hydrate snapshot */
70
+ hydrateSnapshot: (snapshot: ZunoSnapshot) => void;
71
+ /** Get last event ID */
72
+ getLastEventId: () => number;
73
+ /** Set last event ID */
74
+ setLastEventId: (id: number) => void;
75
+ };
76
+ export {};
77
+ //# sourceMappingURL=createZuno.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createZuno.d.ts","sourceRoot":"","sources":["../../src/core/createZuno.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEtD,YAAY;AACZ,KAAK,SAAS,CAAC,CAAC,IAAI;IAClB,GAAG,IAAI,CAAC,CAAC;IACT,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACnB,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,UAAU;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,WAAW;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,eAAe;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,GAAI,OAAM,iBAAsB;IAmRnD,eAAe;;IAEf,gBAAgB;;IAIhB,gBAAgB;eAlIA,CAAC,YAAa,MAAM,QAAQ,MAAM,CAAC;IAoInD,mBAAmB;YArBN,CAAC,YAAa,MAAM,QAAQ,MAAM,CAAC;aAb3C,MAAM;;6CAE0B,OAAO,CAAC,GAAG,CAAC;sCACnB,IAAI,KAAK,MAAM,IAAI;;;IAiCjD,gBAAgB;UA7HL,CAAC,YAAa,MAAM,SAAS,MAAM,CAAC,KAAG,CAAC;IA+HnD,gBAAgB;UAhFC,CAAC,YACR,MAAM,QACV,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,SACnB,MAAM,CAAC;;;;;IA+Ed,yBAAyB;gBA1DR,CAAC,YACR,MAAM,QACV,MAAM,CAAC,MACT,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI;IA2DtB,qBAAqB;sBA7HQ,cAAc;;;;;IA+H3C,WAAW;;IAGX,uBAAuB;gCAhRU,YAAY;IAkR7C,wBAAwB;;IAExB,wBAAwB;yBA3PE,MAAM;CA8PnC,CAAC"}
@@ -0,0 +1,250 @@
1
+ import { createUniverse } from "./universe";
2
+ import { startSSE } from "../sync/sse-client";
3
+ import { startBroadcastChannel } from "../sync/broadcast-channel";
4
+ import { applyIncomingEvent } from "../sync/apply-incoming-event";
5
+ /**
6
+ * Creates a Zuno instance, which provides a state management system with optional synchronization
7
+ * capabilities via Server-Sent Events (SSE) and BroadcastChannel.
8
+ *
9
+ * @param opts - Configuration options for the Zuno instance.
10
+ * @param opts.universe - An optional pre-existing ZunoUniverse instance. If not provided, a new one will be created.
11
+ * @param opts.channelName - An optional name for the BroadcastChannel to enable local tab synchronization.
12
+ * @param opts.sseUrl - The URL for the Server-Sent Events endpoint to receive state updates from a server.
13
+ * @param opts.syncUrl - The URL for the synchronization endpoint to send state updates to a server. Required if `sseUrl` is provided.
14
+ * @param opts.optimistic - A boolean indicating whether state updates should be applied optimistically before server confirmation. Defaults to `true`.
15
+ * @param opts.clientId - A unique identifier for the client. If not provided, a random UUID will be generated.
16
+ * @returns An object containing methods to interact with the Zuno instance, including `getStore`, `destroy`, and `broadcast`.
17
+ */
18
+ export const createZuno = (opts = {}) => {
19
+ /** Local state */
20
+ const localState = new Map();
21
+ /** Local per-store versions (for BC / local ordering) */
22
+ const versions = new Map();
23
+ /** Universe */
24
+ const universe = (opts.universe ?? (createUniverse()));
25
+ /** Unique client ID */
26
+ const clientId = opts.clientId ?? (globalThis.crypto?.randomUUID?.() ?? String(Math.random()));
27
+ /** SSE ready */
28
+ let sseReady = false;
29
+ /** Last event ID */
30
+ let lastEventId = 0;
31
+ /**
32
+ * Hydrate snapshot
33
+ * @param snapshot - The snapshot to hydrate.
34
+ */
35
+ function hydrateSnapshot(snapshot) {
36
+ const plain = {};
37
+ for (const [k, rec] of Object.entries(snapshot.state)) {
38
+ plain[k] = rec.state;
39
+ versions.set(k, rec.version);
40
+ }
41
+ universe.restore(plain);
42
+ lastEventId = snapshot.lastEventId;
43
+ }
44
+ /**
45
+ * Get store base version
46
+ * @param storeKey - The key of the store to get the base version for.
47
+ */
48
+ function getStoreBaseVersion(storeKey) { return versions.get(storeKey) ?? 0; }
49
+ /**
50
+ * Get last event ID
51
+ */
52
+ function getLastEventId() { return lastEventId; }
53
+ /**
54
+ * Set last event ID
55
+ * @param id - The new last event ID.
56
+ */
57
+ function setLastEventId(id) { lastEventId = id; }
58
+ /** SSE Prefer server sync if provided */
59
+ const sse = opts.sseUrl && opts.syncUrl
60
+ ? startSSE({
61
+ universe,
62
+ url: opts.sseUrl,
63
+ syncUrl: opts.syncUrl,
64
+ optimistic: opts.optimistic ?? true,
65
+ clientId,
66
+ versions,
67
+ getLastEventId: () => lastEventId,
68
+ onOpen: () => {
69
+ sseReady = true;
70
+ },
71
+ onClose: () => {
72
+ sseReady = false;
73
+ },
74
+ })
75
+ : null;
76
+ /** Apply event to target */
77
+ const apply = (event) => {
78
+ /** Update last event ID */
79
+ if (typeof event.eventId === "number")
80
+ lastEventId = Math.max(lastEventId, event.eventId);
81
+ applyIncomingEvent(universe, event, { clientId, localState, versions });
82
+ };
83
+ /** Broadcast Channel for local tab sync */
84
+ const bc = opts.channelName
85
+ ? startBroadcastChannel({
86
+ /** Channel name for BroadcastChannel */
87
+ channelName: opts.channelName,
88
+ /** Unique client ID */
89
+ clientId,
90
+ /** Apply event to target */
91
+ onEvent: (ev) => {
92
+ apply(ev);
93
+ },
94
+ /** Get snapshot of local state */
95
+ getSnapshot: () => {
96
+ /** Snapshot */
97
+ const snap = universe.snapshot();
98
+ /** Snapshot */
99
+ const out = {};
100
+ /** Iterate local state */
101
+ for (const [storeKey, state] of Object.entries(snap)) {
102
+ /** Add to snapshot */
103
+ out[storeKey] = {
104
+ state,
105
+ version: versions.get(storeKey) ?? 0,
106
+ };
107
+ }
108
+ return out;
109
+ },
110
+ /** Apply snapshot to target */
111
+ onSnapshot: (snap) => {
112
+ /** Iterate snapshot with store key and record */
113
+ for (const [storeKey, rec] of Object.entries(snap)) {
114
+ /** Record */
115
+ const record = rec;
116
+ /** Get the universe store state or the store state */
117
+ const state = record?.state ?? record;
118
+ /** Get the universe store version or the store version */
119
+ const version = typeof record?.version === "number" ? record.version : 0;
120
+ /** Set the latest version */
121
+ // versions.set(storeKey, Math.max(versions.get(storeKey) ?? 0, version));
122
+ /** Apply the event */
123
+ apply({ storeKey, state, version });
124
+ }
125
+ },
126
+ })
127
+ : null;
128
+ /** Immediately ask other tabs for snapshot (BC-first) */
129
+ setTimeout(() => bc?.hello(), 0);
130
+ /** Store factory
131
+ * @param storeKey - The key of the store to get.
132
+ * @param init - The initialization function for the store.
133
+ * @returns The store.
134
+ */
135
+ const getStore = (storeKey, init) => {
136
+ return universe.getStore(storeKey, init);
137
+ };
138
+ /** Get store by store key
139
+ * @param storeKey - The key of the store to get.
140
+ * @param init - The initialization function for the store.
141
+ * @returns The state of the store.
142
+ */
143
+ const get = (storeKey, init) => {
144
+ return universe.getStore(storeKey, init ?? (() => undefined)).get();
145
+ };
146
+ /** Dispatch event to universe
147
+ * @param event - The event to dispatch.
148
+ * @returns A promise that resolves to the result of the dispatch.
149
+ */
150
+ const dispatch = async (event) => {
151
+ /** Check if SSE is enabled */
152
+ if (sse && sseReady) {
153
+ /** Payload with origin */
154
+ const payload = { ...event, origin: clientId, baseVersion: getStoreBaseVersion(event.storeKey) };
155
+ /** Dispatch to SSE */
156
+ return await sse.dispatch(payload);
157
+ }
158
+ /** Current version */
159
+ const current = versions.get(event.storeKey) ?? 0;
160
+ /** Next version */
161
+ const nextVersion = current + 1;
162
+ /** Apply event with next version */
163
+ apply({ ...event, version: nextVersion });
164
+ /** Set version */
165
+ versions.set(event.storeKey, nextVersion);
166
+ /** Check if BroadcastChannel is enabled */
167
+ if (bc) {
168
+ /** Publish event */
169
+ bc.publish({ ...event, version: nextVersion, origin: clientId });
170
+ }
171
+ /** Return success */
172
+ return { ok: true, status: 200, json: null };
173
+ };
174
+ /** Set store state
175
+ * @param storeKey - The key of the store to set.
176
+ * @param next - The new state to set.
177
+ * @param init - The initialization function for the store.
178
+ * @returns A promise that resolves to the result of the dispatch.
179
+ */
180
+ const set = async (storeKey, next, init) => {
181
+ /** Get store */
182
+ const store = universe.getStore(storeKey, init ?? (() => undefined));
183
+ /** Get previous state */
184
+ const prev = store.get();
185
+ /** Get next state */
186
+ const state = typeof next === "function" ? next(prev) : next;
187
+ /** Dispatch event */
188
+ return dispatch({ storeKey, state });
189
+ };
190
+ /** Subscribe to store
191
+ * @param storeKey - The key of the store to subscribe to.
192
+ * @param init - The initialization function for the store.
193
+ * @param cb - The callback function to be called when the store state changes.
194
+ * @returns A function to unsubscribe from the store.
195
+ */
196
+ const subscribe = (storeKey, init, cb) => {
197
+ /** Get store */
198
+ const store = universe.getStore(storeKey, init);
199
+ return store.subscribe(cb);
200
+ };
201
+ /** Stop cleanup */
202
+ const stop = () => {
203
+ sse?.unsubscribe?.();
204
+ bc?.stop?.();
205
+ };
206
+ /**
207
+ * Creates a bound store for a specific key.
208
+ * @param storeKey The key of the store to create.
209
+ * @param init The initialization function for the store.
210
+ * @returns A BoundStore object representing the store.
211
+ */
212
+ const store = (storeKey, init) => {
213
+ const rawStore = getStore(storeKey, init);
214
+ return {
215
+ key: storeKey,
216
+ raw: () => rawStore,
217
+ get: () => rawStore.get(),
218
+ subscribe: (cb) => rawStore.subscribe(cb),
219
+ set: (next) => set(storeKey, next, init),
220
+ };
221
+ };
222
+ return {
223
+ /** Universe */
224
+ universe,
225
+ /** Client ID */
226
+ clientId,
227
+ // ------------ DX ------------ \\
228
+ /** Get store */
229
+ getStore,
230
+ /** Create store */
231
+ store,
232
+ /** Get state */
233
+ get,
234
+ /** Set state */
235
+ set,
236
+ /** Subscribe to store */
237
+ subscribe,
238
+ // ------------ Advanced ------------ \\
239
+ /** Dispatch event */
240
+ dispatch,
241
+ /** Stop */
242
+ stop,
243
+ /** Hydrate snapshot */
244
+ hydrateSnapshot,
245
+ /** Get last event ID */
246
+ getLastEventId,
247
+ /** Set last event ID */
248
+ setLastEventId,
249
+ };
250
+ };
@@ -0,0 +1,10 @@
1
+ import type { Store } from "./types";
2
+ /**
3
+ * Creates a ZUNO state management store.
4
+ *
5
+ * @template T The type of the state managed by the store.
6
+ * @param {T} initial The initial state value.
7
+ * @returns {Store<T>} An object containing methods to interact with the store.
8
+ */
9
+ export declare const createStore: <T>(initial: T) => Store<T>;
10
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,SAAS,CAAC,KAAG,KAAK,CAAC,CAAC,CAsBlD,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Creates a ZUNO state management store.
3
+ *
4
+ * @template T The type of the state managed by the store.
5
+ * @param {T} initial The initial state value.
6
+ * @returns {Store<T>} An object containing methods to interact with the store.
7
+ */
8
+ export const createStore = (initial) => {
9
+ let state = initial;
10
+ const listeners = new Set();
11
+ return {
12
+ get: () => state,
13
+ set: (next) => {
14
+ const value = typeof next === "function"
15
+ ? next(state)
16
+ : next;
17
+ if (Object.is(value, state))
18
+ return;
19
+ state = value;
20
+ listeners.forEach((l) => l(state));
21
+ },
22
+ subscribe: (listener) => {
23
+ listeners.add(listener);
24
+ return () => listeners.delete(listener);
25
+ }
26
+ };
27
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Represents a generic store that can hold and manage a state of type T.
3
+ * It provides methods to get, set, and subscribe to changes in the state.
4
+ */
5
+ export interface Store<T> {
6
+ /**
7
+ * Retrieves the current value of the store's state.
8
+ * @returns The current state.
9
+ */
10
+ get(): T;
11
+ /**
12
+ * Sets the new state of the store.
13
+ * It can accept either a direct value or a function that receives the previous state
14
+ * and returns the new state.
15
+ * @param next The new state value or a function to derive the new state.
16
+ */
17
+ set(next: T | ((prev: T) => T)): void;
18
+ /**
19
+ * Subscribes a listener function to state changes.
20
+ * The listener will be called whenever the state updates.
21
+ * @param listener The function to call when the state changes.
22
+ * @returns {boolean} A function to unsubscribe the listener. Calling this function will remove the subscription.
23
+ */
24
+ subscribe(listener: (state: T) => void): () => boolean;
25
+ }
26
+ /**
27
+ * Represents a global container or manager for multiple stores,
28
+ * often referred to as a "universe" in state management patterns.
29
+ * It allows creating, retrieving, snapshotting, and restoring stores.
30
+ */
31
+ export interface Universe {
32
+ /**
33
+ * Retrieves a store by its unique key. If the store does not exist,
34
+ * it initializes it using the provided `init` function and then returns it.
35
+ * @param key The unique identifier for the store.
36
+ * @param init A function to initialize the store's state if it doesn't already exist.
37
+ * @returns The requested store.
38
+ */
39
+ getStore<T>(key: string, init: () => T): Store<T>;
40
+ /**
41
+ * Creates a snapshot of the current state of all stores managed by the Universe.
42
+ * The snapshot is a record where keys are store identifiers and values are their states.
43
+ * @returns A record representing the current state of all stores.
44
+ */
45
+ snapshot(): Record<string, unknown>;
46
+ /**
47
+ * Restores the state of all stores in the Universe from a provided data snapshot.
48
+ * This can be used to rehydrate the application's state.
49
+ * @param data A record containing the state to restore for each store.
50
+ */
51
+ restore(data: Record<string, unknown>): void;
52
+ /**
53
+ * Deletes a store by its unique key.
54
+ * @param key The unique identifier for the store to delete.
55
+ */
56
+ delete(key: string): void;
57
+ /**
58
+ * Clears all stores from the Universe.
59
+ */
60
+ clear(): void;
61
+ /**
62
+ * Restores the state of all stores in the Universe from a provided data snapshot.
63
+ * This can be used to rehydrate the application's state.
64
+ * @param snapshot A record containing the state to restore for each store.
65
+ */
66
+ hydrateSnapshot(snapshot: ZunoSnapshot): void;
67
+ }
68
+ /**
69
+ * Context for incoming events.
70
+ * It is used to prevent loops (don’t re-apply your own broadcast) and to track per-store version tracking (SSE uses it, BC can ignore it).
71
+ */
72
+ export type IncomingEventContext = {
73
+ /** Used to prevent loops (don’t re-apply your own broadcast) */
74
+ clientId: string;
75
+ /** Per-store version tracking (SSE uses it, BC can ignore it) */
76
+ versions?: Map<string, number>;
77
+ /** Optional in-memory snapshot state for BC “hello/snapshot” */
78
+ localState?: Map<string, unknown>;
79
+ };
80
+ /**
81
+ * A snapshot of the universe state.
82
+ * It is used to rehydrate the application's state.
83
+ */
84
+ export type ZunoSnapshot = {
85
+ state: Record<string, {
86
+ state: unknown;
87
+ version: number;
88
+ }>;
89
+ lastEventId: number;
90
+ };
91
+ /**
92
+ * A contract for adapters to interact with the store in a read-only manner.
93
+ * This provides a "native feel" for consumers preventing them from touching the core directly.
94
+ */
95
+ export type ZunoReadable<T> = {
96
+ /** Read current value (must be sync) */
97
+ getSnapshot(): T;
98
+ /**
99
+ * Subscribe to changes.
100
+ * Must call `onChange` whenever snapshot may have changed.
101
+ * Returns unsubscribe.
102
+ */
103
+ subscribe(onChange: () => void): () => void;
104
+ /** Optional: for SSR hydration safety in React */
105
+ getServerSnapshot?: () => T;
106
+ };
107
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,KAAK,CAAC,CAAC;IACtB;;;OAGG;IACH,GAAG,IAAI,CAAC,CAAC;IACT;;;;;OAKG;IACH,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;IACtC;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC;CACxD;AAED;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAClD;;;;OAIG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAE7C;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IAEd;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;CAE/C;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IAEjB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE/B,gEAAgE;IAChE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC1D,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,wCAAwC;IACxC,WAAW,IAAI,CAAC,CAAC;IAEjB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAE5C,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;CAC7B,CAAC"}
@@ -0,0 +1 @@
1
+ export {};