@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.
Files changed (72) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +7 -11
  3. package/dist/index-IbvF6TBr.d.cts +151 -0
  4. package/dist/index-IbvF6TBr.d.ts +151 -0
  5. package/dist/index.cjs +2 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/{shared/readable.d.ts → index.d.cts} +7 -4
  8. package/dist/index.d.ts +24 -10
  9. package/dist/index.js +2 -6
  10. package/dist/index.js.map +1 -0
  11. package/dist/server/index.cjs +17 -0
  12. package/dist/server/index.cjs.map +1 -0
  13. package/dist/server/index.d.cts +57 -0
  14. package/dist/server/index.d.ts +57 -4
  15. package/dist/server/index.js +17 -3
  16. package/dist/server/index.js.map +1 -0
  17. package/package.json +9 -5
  18. package/dist/core/createZuno.d.ts +0 -77
  19. package/dist/core/createZuno.d.ts.map +0 -1
  20. package/dist/core/createZuno.js +0 -250
  21. package/dist/core/store.d.ts +0 -10
  22. package/dist/core/store.d.ts.map +0 -1
  23. package/dist/core/store.js +0 -27
  24. package/dist/core/types.d.ts +0 -107
  25. package/dist/core/types.d.ts.map +0 -1
  26. package/dist/core/types.js +0 -1
  27. package/dist/core/universe.d.ts +0 -12
  28. package/dist/core/universe.d.ts.map +0 -1
  29. package/dist/core/universe.js +0 -53
  30. package/dist/index.d.ts.map +0 -1
  31. package/dist/server/apply-state-event.d.ts +0 -26
  32. package/dist/server/apply-state-event.d.ts.map +0 -1
  33. package/dist/server/apply-state-event.js +0 -29
  34. package/dist/server/index.d.ts.map +0 -1
  35. package/dist/server/server-transport.d.ts +0 -8
  36. package/dist/server/server-transport.d.ts.map +0 -1
  37. package/dist/server/server-transport.js +0 -25
  38. package/dist/server/snapshot-handler.d.ts +0 -9
  39. package/dist/server/snapshot-handler.d.ts.map +0 -1
  40. package/dist/server/snapshot-handler.js +0 -18
  41. package/dist/server/sse-handler.d.ts +0 -24
  42. package/dist/server/sse-handler.d.ts.map +0 -1
  43. package/dist/server/sse-handler.js +0 -127
  44. package/dist/server/state.bus.d.ts +0 -18
  45. package/dist/server/state.bus.d.ts.map +0 -1
  46. package/dist/server/state.bus.js +0 -26
  47. package/dist/server/state.log.d.ts +0 -22
  48. package/dist/server/state.log.d.ts.map +0 -1
  49. package/dist/server/state.log.js +0 -37
  50. package/dist/server/types.d.ts +0 -8
  51. package/dist/server/types.d.ts.map +0 -1
  52. package/dist/server/types.js +0 -1
  53. package/dist/server/universe-store.d.ts +0 -29
  54. package/dist/server/universe-store.d.ts.map +0 -1
  55. package/dist/server/universe-store.js +0 -26
  56. package/dist/shared/readable.d.ts.map +0 -1
  57. package/dist/shared/readable.js +0 -7
  58. package/dist/sync/apply-incoming-event.d.ts +0 -10
  59. package/dist/sync/apply-incoming-event.d.ts.map +0 -1
  60. package/dist/sync/apply-incoming-event.js +0 -28
  61. package/dist/sync/broadcast-channel.d.ts +0 -12
  62. package/dist/sync/broadcast-channel.d.ts.map +0 -1
  63. package/dist/sync/broadcast-channel.js +0 -73
  64. package/dist/sync/sse-client.d.ts +0 -21
  65. package/dist/sync/sse-client.d.ts.map +0 -1
  66. package/dist/sync/sse-client.js +0 -162
  67. package/dist/sync/sync-types.d.ts +0 -164
  68. package/dist/sync/sync-types.d.ts.map +0 -1
  69. package/dist/sync/sync-types.js +0 -1
  70. package/dist/sync/transport.d.ts +0 -10
  71. package/dist/sync/transport.d.ts.map +0 -1
  72. package/dist/sync/transport.js +0 -26
@@ -1,107 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,12 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,53 +0,0 @@
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
- };
@@ -1 +0,0 @@
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"}
@@ -1,26 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,29 +0,0 @@
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
- };
@@ -1 +0,0 @@
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"}
@@ -1,8 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,25 +0,0 @@
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
- };
@@ -1,9 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,18 +0,0 @@
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
- }
@@ -1,24 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,127 +0,0 @@
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
- };
@@ -1,18 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,26 +0,0 @@
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
- };
@@ -1,22 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,37 +0,0 @@
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
- };
@@ -1,8 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
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
@@ -1 +0,0 @@
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"}