@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
@@ -0,0 +1,12 @@
1
+ import type { Universe } from "./types";
2
+ /**
3
+ * Creates a ZUNO Universe.
4
+ *
5
+ * A Universe is a simple container that manages multiple named stores.
6
+ * It:
7
+ * - Lazily creates a store when `getStore` is called with a new key
8
+ * - Can snapshot the state of all known stores
9
+ * - Can restore state from a snapshot
10
+ */
11
+ export declare const createUniverse: () => Universe;
12
+ //# sourceMappingURL=universe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"universe.d.ts","sourceRoot":"","sources":["../../src/core/universe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAuB,MAAM,SAAS,CAAC;AAG7D;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,QAAO,QAoDjC,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { createStore } from "./store";
2
+ /**
3
+ * Creates a ZUNO Universe.
4
+ *
5
+ * A Universe is a simple container that manages multiple named stores.
6
+ * It:
7
+ * - Lazily creates a store when `getStore` is called with a new key
8
+ * - Can snapshot the state of all known stores
9
+ * - Can restore state from a snapshot
10
+ */
11
+ export const createUniverse = () => {
12
+ const stores = new Map();
13
+ return {
14
+ getStore(key, init) {
15
+ if (!stores.has(key)) {
16
+ const initialState = init();
17
+ stores.set(key, createStore(initialState));
18
+ }
19
+ // TypeScript doesn't know the concrete T stored under this key,
20
+ // but by convention you control init(), so this cast is safe.
21
+ return stores.get(key);
22
+ },
23
+ snapshot() {
24
+ const out = {};
25
+ for (const [key, store] of stores.entries()) {
26
+ out[key] = store.get();
27
+ }
28
+ return out;
29
+ },
30
+ restore(data) {
31
+ for (const [key, value] of Object.entries(data)) {
32
+ const existing = stores.get(key);
33
+ if (existing) {
34
+ existing.set(value);
35
+ }
36
+ else {
37
+ // create a new store with this initial value
38
+ const newStore = createStore(value);
39
+ stores.set(key, newStore);
40
+ }
41
+ }
42
+ },
43
+ delete(key) {
44
+ stores.delete(key);
45
+ },
46
+ clear() {
47
+ stores.clear();
48
+ },
49
+ hydrateSnapshot(snapshot) {
50
+ this.restore(snapshot.state);
51
+ }
52
+ };
53
+ };
@@ -0,0 +1,10 @@
1
+ export { createZuno } from "./core/createZuno";
2
+ export type { CreateZunoOptions } from "./core/createZuno";
3
+ export type * from "./core/types";
4
+ export { startSSE } from "./sync/sse-client";
5
+ export { startBroadcastChannel } from "./sync/broadcast-channel";
6
+ export type * from "./sync/sync-types";
7
+ export * from "./sync/transport";
8
+ export type { ZunoReadable, ZunoSubscribableStore } from "./shared/readable";
9
+ export { toReadable } from "./shared/readable";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,mBAAmB,cAAc,CAAC;AAGlC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,mBAAmB,mBAAmB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AAGjC,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { createZuno } from "./core/createZuno";
2
+ // Sync (client)
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";
@@ -0,0 +1,26 @@
1
+ import type { ZunoStateEvent } from "../sync/sync-types";
2
+ /**
3
+ * Result of applying a state event.
4
+ * If the event was applied successfully, `ok` is true and `event` contains the applied event.
5
+ * If there was a version conflict, `ok` is false and `reason` is "VERSION_CONFLICT".
6
+ */
7
+ export type ApplyResult = {
8
+ ok: true;
9
+ event: ZunoStateEvent;
10
+ } | {
11
+ ok: false;
12
+ reason: "VERSION_CONFLICT";
13
+ current: {
14
+ state: any;
15
+ version: number;
16
+ };
17
+ };
18
+ /**
19
+ * Core sync handler that applies an event to the universe
20
+ * and broadcasts it to all SSE subscribers.
21
+ * This is independent of HTTP / WebSocket / whatever transport.
22
+ * @property {ZunoStateEvent} incoming - The incoming event to apply.
23
+ * @returns {ApplyResult} The result of the application.
24
+ */
25
+ export declare const applyStateEvent: (incoming: ZunoStateEvent) => ApplyResult;
26
+ //# sourceMappingURL=apply-state-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-state-event.d.ts","sourceRoot":"","sources":["../../src/server/apply-state-event.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE;QAAE,KAAK,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAExF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,cAAc,KAAG,WAwB1D,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { publishToStateEvent } from "./state.bus";
2
+ import { appendEvent } from "./state.log";
3
+ import { getUniverseRecord, updateUniverseState } from "./universe-store";
4
+ /**
5
+ * Core sync handler that applies an event to the universe
6
+ * and broadcasts it to all SSE subscribers.
7
+ * This is independent of HTTP / WebSocket / whatever transport.
8
+ * @property {ZunoStateEvent} incoming - The incoming event to apply.
9
+ * @returns {ApplyResult} The result of the application.
10
+ */
11
+ export const applyStateEvent = (incoming) => {
12
+ /** Get the current state of the store */
13
+ const current = getUniverseRecord(incoming.storeKey) ?? { state: undefined, version: 0 };
14
+ /** Only enforce if client provided baseVersion */
15
+ if (typeof incoming.baseVersion === "number" && incoming.baseVersion !== current.version) {
16
+ return { ok: false, reason: "VERSION_CONFLICT", current };
17
+ }
18
+ /** Create a new event with the next version and current timestamp */
19
+ const event = appendEvent({
20
+ ...incoming,
21
+ version: current.version + 1,
22
+ ts: Date.now(),
23
+ });
24
+ /** Update the universe state */
25
+ updateUniverseState(event);
26
+ /** Publish the event */
27
+ publishToStateEvent(event);
28
+ return { ok: true, event };
29
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./snapshot-handler";
2
+ export { createSSEConnection, setUniverseState } from "./sse-handler";
3
+ export * from "./apply-state-event";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,OAAO,EACL,mBAAmB,EAAE,gBAAgB,EACtC,MAAM,eAAe,CAAC;AACvB,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./snapshot-handler";
2
+ export { createSSEConnection, setUniverseState } from "./sse-handler";
3
+ export * from "./apply-state-event";
@@ -0,0 +1,8 @@
1
+ import type { ZunoTransport } from "../sync/sync-types";
2
+ /**
3
+ * Creates a server transport for Zuno.
4
+ *
5
+ * @returns A ZunoTransport instance.
6
+ */
7
+ export declare const createServerTransport: () => ZunoTransport;
8
+ //# sourceMappingURL=server-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-transport.d.ts","sourceRoot":"","sources":["../../src/server/server-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAO,aAoBxC,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Creates a server transport for Zuno.
3
+ *
4
+ * @returns A ZunoTransport instance.
5
+ */
6
+ export const createServerTransport = () => {
7
+ /**
8
+ * The set of subscribers to state events.
9
+ */
10
+ const subs = new Set();
11
+ /**
12
+ * Publishes a state event to all subscribers.
13
+ */
14
+ return {
15
+ async publish(event) {
16
+ for (const cb of subs)
17
+ cb(event);
18
+ return { ok: true, status: 200, json: null };
19
+ },
20
+ subscribe(cb) {
21
+ subs.add(cb);
22
+ return () => subs.delete(cb);
23
+ },
24
+ };
25
+ };
@@ -0,0 +1,9 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ /**
3
+ * Sends a snapshot of the universe state to the client.
4
+ *
5
+ * @param _req The incoming HTTP request object.
6
+ * @param res The server response object, used to send the JSON universe state.
7
+ */
8
+ export declare function sendSnapshot(_req: IncomingMessage, res: ServerResponse): void;
9
+ //# sourceMappingURL=snapshot-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-handler.d.ts","sourceRoot":"","sources":["../../src/server/snapshot-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAI3D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,QAStE"}
@@ -0,0 +1,18 @@
1
+ import { getUniverseState } from "./universe-store";
2
+ import { getLastEventId } from "./state.log";
3
+ /**
4
+ * Sends a snapshot of the universe state to the client.
5
+ *
6
+ * @param _req The incoming HTTP request object.
7
+ * @param res The server response object, used to send the JSON universe state.
8
+ */
9
+ export function sendSnapshot(_req, res) {
10
+ const body = {
11
+ state: getUniverseState(),
12
+ version: getUniverseState().version ?? 0,
13
+ lastEventId: getLastEventId(),
14
+ };
15
+ res.statusCode = 200;
16
+ res.setHeader("Content-Type", "application/json");
17
+ res.end(JSON.stringify(body));
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ type IncomingHeaders = IncomingMessage["headers"];
3
+ /**
4
+ * Creates a Server-Sent Events (SSE) connection for Zuno state updates.
5
+ */
6
+ export declare const createSSEConnection: (req: IncomingMessage, res: ServerResponse, headers: IncomingHeaders) => void;
7
+ /**
8
+ * Synchronizes the Zuno universe state by applying an incoming event.
9
+ *
10
+ * This endpoint accepts a POST request with a JSON body representing a `ZunoStateEvent`.
11
+ * It updates the universe state and then publishes the event to all SSE subscribers.
12
+ *
13
+ * @param req The incoming HTTP request object, expected to contain a JSON `ZunoStateEvent` in its body.
14
+ * @param res The server response object, used to acknowledge the update or report errors.
15
+ * @param transport The transport object used to publish the event to all SSE subscribers.
16
+ */
17
+ export declare const syncUniverseState: (req: IncomingMessage, res: ServerResponse) => void;
18
+ /**
19
+ * Sets the universe state to a specific version.
20
+ * Backwards-compatible alias of syncUniverseState.
21
+ */
22
+ export declare const setUniverseState: (req: IncomingMessage, res: ServerResponse) => void;
23
+ export {};
24
+ //# sourceMappingURL=sse-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-handler.d.ts","sourceRoot":"","sources":["../../src/server/sse-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAK5D,KAAK,eAAe,GAAG,eAAe,CAAC,SAAS,CAAC,CAAA;AAEjD;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,SAAS,eAAe,SA0EtG,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,eAAe,EAAE,KAAK,cAAc,SAwC1E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,eAAe,EAAE,KAAK,cAAc,SAEzE,CAAC"}
@@ -0,0 +1,127 @@
1
+ import { subscribeToStateEvents } from "./state.bus";
2
+ import { getUniverseState } from "./universe-store";
3
+ import { applyStateEvent } from "./apply-state-event";
4
+ import { getEventsAfter } from "./state.log";
5
+ /**
6
+ * Creates a Server-Sent Events (SSE) connection for Zuno state updates.
7
+ */
8
+ export const createSSEConnection = (req, res, headers) => {
9
+ res.writeHead(200, {
10
+ "Cache-Control": "no-cache, no-transform",
11
+ "Content-Type": "text/event-stream; charset=utf-8",
12
+ Connection: "keep-alive",
13
+ "X-Accel-Buffering": "no",
14
+ ...headers
15
+ });
16
+ /**
17
+ * Immediately flushes the response headers to the client.
18
+ * This is crucial for SSE to ensure the client receives headers and
19
+ * starts processing the event stream without buffering delays.
20
+ */
21
+ res.flushHeaders?.();
22
+ /** Get the last event id from header `last-event-id` or query param `lastEventId` */
23
+ const raw = req.headers["last-event-id"] || new URL(req.url || "", "http://localhost").searchParams.get("lastEventId");
24
+ const lastEventId = Number.parseInt(Array.isArray(raw) ? raw[0] : (raw ?? "0"), 10) || 0;
25
+ /**
26
+ * If the client has a `last-event-id`, it means it's reconnecting after a disconnect.
27
+ * In this case, we need to send it any missed events since the last event it received.
28
+ */
29
+ if (lastEventId > 0) {
30
+ /** Get the events that occurred after the last event the client received. */
31
+ const missed = getEventsAfter(lastEventId);
32
+ /** Send the missed events to the client. */
33
+ for (const event of missed) {
34
+ res.write(`id: ${event.eventId}\n`);
35
+ res.write(`event: state\n`);
36
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
37
+ }
38
+ }
39
+ /**
40
+ * If the client doesn't have a `last-event-id`, it means it's a fresh connection.
41
+ * In this case, we need to send it the current state of the universe.
42
+ */
43
+ else {
44
+ /** Send the current state of the universe to the client. */
45
+ res.write(`event: snapshot\n`);
46
+ res.write(`data: ${JSON.stringify(getUniverseState())}\n\n`);
47
+ }
48
+ /** Subscribe to state events and send them to the client */
49
+ const unsubscribe = subscribeToStateEvents((event) => {
50
+ res.write(`id: ${event.eventId}\n`);
51
+ res.write(`event: state\n`);
52
+ res.write(`data: ${JSON.stringify(event)}\n\n`); // The actual event data
53
+ });
54
+ /**
55
+ * Set up a heartbeat mechanism to keep the SSE connection alive.
56
+ * A ping event is sent every 15 seconds.
57
+ */
58
+ const heartbeat = setInterval(() => {
59
+ res.write(`: ping ${Date.now()}\n\n`);
60
+ }, 15000);
61
+ /**
62
+ * Send an initial connection message to the client.
63
+ * This helps the client know when the connection is established.
64
+ */
65
+ res.write(": connected \n\n");
66
+ /**
67
+ * Clean up subscription when the client disconnects.
68
+ */
69
+ req.on("close", () => {
70
+ clearInterval(heartbeat);
71
+ unsubscribe();
72
+ res.end();
73
+ });
74
+ };
75
+ /**
76
+ * Synchronizes the Zuno universe state by applying an incoming event.
77
+ *
78
+ * This endpoint accepts a POST request with a JSON body representing a `ZunoStateEvent`.
79
+ * It updates the universe state and then publishes the event to all SSE subscribers.
80
+ *
81
+ * @param req The incoming HTTP request object, expected to contain a JSON `ZunoStateEvent` in its body.
82
+ * @param res The server response object, used to acknowledge the update or report errors.
83
+ * @param transport The transport object used to publish the event to all SSE subscribers.
84
+ */
85
+ export const syncUniverseState = (req, res) => {
86
+ const MAX_BODY_BYTES = 512 * 1024; // 512KB safety
87
+ let body = "";
88
+ // Accumulate data chunks from the request body
89
+ req.on("data", (chunk) => {
90
+ body += chunk.toString("utf8");
91
+ if (body.length > MAX_BODY_BYTES) {
92
+ res.writeHead(413, { "Content-Type": "application/json" });
93
+ res.end(JSON.stringify({ ok: false, reason: "PAYLOAD_TOO_LARGE" }));
94
+ req.destroy();
95
+ }
96
+ });
97
+ req.on("end", () => {
98
+ try {
99
+ const incoming = JSON.parse(body || "{}");
100
+ const result = applyStateEvent(incoming); // ✅ core sync
101
+ if (!result.ok) {
102
+ if (result.reason === "VERSION_CONFLICT") {
103
+ res.writeHead(409, { "Content-Type": "application/json" });
104
+ res.end(JSON.stringify({
105
+ ok: false,
106
+ reason: "VERSION_CONFLICT",
107
+ current: result.current,
108
+ }));
109
+ }
110
+ return;
111
+ }
112
+ res.writeHead(200, { "Content-Type": "application/json" });
113
+ res.end(JSON.stringify({ ok: true, event: result.event }));
114
+ }
115
+ catch {
116
+ res.writeHead(400, { "Content-Type": "application/json" });
117
+ res.end(JSON.stringify({ ok: false, reason: "INVALID_JSON" }));
118
+ }
119
+ });
120
+ };
121
+ /**
122
+ * Sets the universe state to a specific version.
123
+ * Backwards-compatible alias of syncUniverseState.
124
+ */
125
+ export const setUniverseState = (req, res) => {
126
+ return syncUniverseState(req, res);
127
+ };
@@ -0,0 +1,18 @@
1
+ import type { ZunoStateEvent } from "../sync/sync-types";
2
+ import type { ZunoStateListener } from "./types";
3
+ /**
4
+ * Subscribes a listener function to state events.
5
+ * The listener will be called whenever a new state event is published.
6
+ *
7
+ * @param listener The function to be called when a state event occurs.
8
+ * @returns A cleanup function that, when called, unsubscribes the listener.
9
+ */
10
+ export declare const subscribeToStateEvents: (listener: ZunoStateListener) => () => void;
11
+ /**
12
+ * Publishes a state event to all registered listeners.
13
+ * Each subscribed listener will receive the event.
14
+ *
15
+ * @param event The state event to be published.
16
+ */
17
+ export declare const publishToStateEvent: (event: ZunoStateEvent) => void;
18
+ //# sourceMappingURL=state.bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.bus.d.ts","sourceRoot":"","sources":["../../src/server/state.bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAOjD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,UAAU,iBAAiB,eAKjE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,cAAc,SAExD,CAAA"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The set of listeners subscribed to state events.
3
+ */
4
+ const listeners = new Set();
5
+ /**
6
+ * Subscribes a listener function to state events.
7
+ * The listener will be called whenever a new state event is published.
8
+ *
9
+ * @param listener The function to be called when a state event occurs.
10
+ * @returns A cleanup function that, when called, unsubscribes the listener.
11
+ */
12
+ export const subscribeToStateEvents = (listener) => {
13
+ listeners.add(listener);
14
+ return () => {
15
+ listeners.delete(listener);
16
+ };
17
+ };
18
+ /**
19
+ * Publishes a state event to all registered listeners.
20
+ * Each subscribed listener will receive the event.
21
+ *
22
+ * @param event The state event to be published.
23
+ */
24
+ export const publishToStateEvent = (event) => {
25
+ listeners.forEach(listener => listener(event));
26
+ };
@@ -0,0 +1,22 @@
1
+ import type { ZunoStateEvent } from "../sync/sync-types";
2
+ /**
3
+ * Appends an event to the log.
4
+ *
5
+ * @param event The event to append.
6
+ * @returns The appended event.
7
+ */
8
+ export declare const appendEvent: (event: ZunoStateEvent) => ZunoStateEvent;
9
+ /**
10
+ * Returns events after the given event id.
11
+ *
12
+ * @param lastEventId The last event id to return events after.
13
+ * @returns The events after the given event id.
14
+ */
15
+ export declare const getEventsAfter: (lastEventId: number) => ZunoStateEvent[];
16
+ /**
17
+ * Returns the last event id in the log.
18
+ *
19
+ * @returns The last event id in the log.
20
+ */
21
+ export declare const getLastEventId: () => number;
22
+ //# sourceMappingURL=state.log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.log.d.ts","sourceRoot":"","sources":["../../src/server/state.log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAWzD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,cAAc,mBAShD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,qBAEjD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,cAE1B,CAAA"}
@@ -0,0 +1,37 @@
1
+ /** Maximum number of events to keep in the log. */
2
+ const MAX_EVENTS = 1000;
3
+ /** The next event id to assign. */
4
+ let nextEventId = 1;
5
+ /** The log of events. */
6
+ const eventLog = [];
7
+ /**
8
+ * Appends an event to the log.
9
+ *
10
+ * @param event The event to append.
11
+ * @returns The appended event.
12
+ */
13
+ export const appendEvent = (event) => {
14
+ event.eventId = nextEventId++;
15
+ eventLog.push(event);
16
+ if (eventLog.length > MAX_EVENTS) {
17
+ eventLog.shift();
18
+ }
19
+ return event;
20
+ };
21
+ /**
22
+ * Returns events after the given event id.
23
+ *
24
+ * @param lastEventId The last event id to return events after.
25
+ * @returns The events after the given event id.
26
+ */
27
+ export const getEventsAfter = (lastEventId) => {
28
+ return eventLog.filter(event => (event?.eventId ?? 0) > lastEventId);
29
+ };
30
+ /**
31
+ * Returns the last event id in the log.
32
+ *
33
+ * @returns The last event id in the log.
34
+ */
35
+ export const getLastEventId = () => {
36
+ return eventLog[eventLog.length - 1]?.eventId ?? 0;
37
+ };
@@ -0,0 +1,8 @@
1
+ import type { ZunoStateEvent } from "../sync/sync-types";
2
+ /**
3
+ * A callback function type for listening to Zuno state events.
4
+ *
5
+ * @param event The Zuno state event that occurred.
6
+ */
7
+ export type ZunoStateListener = (event: ZunoStateEvent) => void;
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { ZunoStateEvent } from "../sync/sync-types";
2
+ /**
3
+ * A global map to store the state of different parts of the universe.
4
+ * The keys are `storeKey` strings and the values are the corresponding state objects.
5
+ */
6
+ type UniverseRecord = {
7
+ state: any;
8
+ version: number;
9
+ };
10
+ /**
11
+ * Retrieves the current state of a specific store in the universe.
12
+ * @param storeKey The key of the store to retrieve.
13
+ * @returns The current state of the store, or undefined if the store does not exist.
14
+ */
15
+ export declare const getUniverseRecord: (storeKey: string) => UniverseRecord | undefined;
16
+ /**
17
+ * Updates the state of a specific store in the universe.
18
+ * @param event The ZunoStateEvent containing the storeKey and the new state to set.
19
+ */
20
+ export declare const updateUniverseState: (event: ZunoStateEvent) => void;
21
+ /**
22
+ * Retrieves the current state of the entire universe.
23
+ * @returns An object containing the state of all stores in the universe.
24
+ */
25
+ export declare const getUniverseState: () => {
26
+ [k: string]: UniverseRecord;
27
+ };
28
+ export {};
29
+ //# sourceMappingURL=universe-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"universe-store.d.ts","sourceRoot":"","sources":["../../src/server/universe-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;GAGG;AACH,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,GAAG,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,cAAc,GAAG,SAErE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,cAAc,SAQxD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;CAE5B,CAAC"}
@@ -0,0 +1,26 @@
1
+ const universeState = new Map();
2
+ /**
3
+ * Retrieves the current state of a specific store in the universe.
4
+ * @param storeKey The key of the store to retrieve.
5
+ * @returns The current state of the store, or undefined if the store does not exist.
6
+ */
7
+ export const getUniverseRecord = (storeKey) => {
8
+ return universeState.get(storeKey);
9
+ };
10
+ /**
11
+ * Updates the state of a specific store in the universe.
12
+ * @param event The ZunoStateEvent containing the storeKey and the new state to set.
13
+ */
14
+ export const updateUniverseState = (event) => {
15
+ const current = universeState.get(event.storeKey) ?? { state: undefined, version: 0 };
16
+ // If applyStateEvent already computed event.version, prefer it.
17
+ const nextVersion = typeof event.version === "number" ? event.version : current.version + 1;
18
+ universeState.set(event.storeKey, { state: event.state, version: nextVersion });
19
+ };
20
+ /**
21
+ * Retrieves the current state of the entire universe.
22
+ * @returns An object containing the state of all stores in the universe.
23
+ */
24
+ export const getUniverseState = () => {
25
+ return Object.fromEntries(universeState);
26
+ };
@@ -0,0 +1,21 @@
1
+ /** Universal UI adapter contract */
2
+ export type ZunoReadable<T> = {
3
+ /** Read current value (sync) */
4
+ getSnapshot(): T;
5
+ /**
6
+ * Subscribe to changes.
7
+ * Call `onChange()` whenever snapshot may have changed.
8
+ * Return unsubscribe.
9
+ */
10
+ subscribe(onChange: () => void): () => void;
11
+ /** Optional: React SSR (server snapshot) */
12
+ getServerSnapshot?: () => T;
13
+ };
14
+ /** Minimal store shape that can be adapted into a readable */
15
+ export type ZunoSubscribableStore<T> = {
16
+ get(): T;
17
+ subscribe(cb: (state: T) => void): () => void;
18
+ };
19
+ /** Adapter helper: convert store => readable */
20
+ export declare function toReadable<T>(store: ZunoSubscribableStore<T>): ZunoReadable<T>;
21
+ //# sourceMappingURL=readable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readable.d.ts","sourceRoot":"","sources":["../../src/shared/readable.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,gCAAgC;IAChC,WAAW,IAAI,CAAC,CAAC;IAEjB;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAE5C,4CAA4C;IAC5C,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;CAC7B,CAAC;AAEF,8DAA8D;AAC9D,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI;IACrC,GAAG,IAAI,CAAC,CAAC;IACT,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC/C,CAAC;AAEF,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAK9E"}
@@ -0,0 +1,7 @@
1
+ /** Adapter helper: convert store => readable */
2
+ export function toReadable(store) {
3
+ return {
4
+ getSnapshot: () => store.get(),
5
+ subscribe: (onChange) => store.subscribe(() => onChange()),
6
+ };
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { IncomingEventContext, Universe } from "../core/types";
2
+ import type { ZunoStateEvent } from "./sync-types";
3
+ /**
4
+ * Applies an incoming event to the Universe in a safe, reusable way.
5
+ * @property universe - The universe to apply the event to.
6
+ * @property event - The event to apply.
7
+ * @property ctx - The context for the event.
8
+ **/
9
+ export declare function applyIncomingEvent(universe: Universe, event: ZunoStateEvent, ctx: IncomingEventContext): void;
10
+ //# sourceMappingURL=apply-incoming-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-incoming-event.d.ts","sourceRoot":"","sources":["../../src/sync/apply-incoming-event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;;IAKI;AACJ,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,oBAAoB,QA4B1B"}