@geometra/server 1.0.0 → 1.0.1

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 (34) hide show
  1. package/README.md +5 -2
  2. package/dist/binary-frame.d.ts +13 -0
  3. package/dist/binary-frame.d.ts.map +1 -0
  4. package/dist/binary-frame.js +39 -0
  5. package/dist/binary-frame.js.map +1 -0
  6. package/dist/index.d.ts +4 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/protocol.d.ts +9 -0
  11. package/dist/protocol.d.ts.map +1 -1
  12. package/dist/protocol.js +4 -0
  13. package/dist/protocol.js.map +1 -1
  14. package/dist/server.d.ts +30 -0
  15. package/dist/server.d.ts.map +1 -1
  16. package/dist/server.js +95 -8
  17. package/dist/server.js.map +1 -1
  18. package/package.json +2 -2
  19. package/dist/__tests__/protocol-compat.test.d.ts +0 -2
  20. package/dist/__tests__/protocol-compat.test.d.ts.map +0 -1
  21. package/dist/__tests__/protocol-compat.test.js +0 -77
  22. package/dist/__tests__/protocol-compat.test.js.map +0 -1
  23. package/dist/__tests__/protocol-diff.test.d.ts +0 -2
  24. package/dist/__tests__/protocol-diff.test.d.ts.map +0 -1
  25. package/dist/__tests__/protocol-diff.test.js +0 -91
  26. package/dist/__tests__/protocol-diff.test.js.map +0 -1
  27. package/dist/__tests__/protocol-fixture.test.d.ts +0 -2
  28. package/dist/__tests__/protocol-fixture.test.d.ts.map +0 -1
  29. package/dist/__tests__/protocol-fixture.test.js +0 -20
  30. package/dist/__tests__/protocol-fixture.test.js.map +0 -1
  31. package/dist/__tests__/protocol-perf-smoke.test.d.ts +0 -2
  32. package/dist/__tests__/protocol-perf-smoke.test.d.ts.map +0 -1
  33. package/dist/__tests__/protocol-perf-smoke.test.js +0 -68
  34. package/dist/__tests__/protocol-perf-smoke.test.js.map +0 -1
package/README.md CHANGED
@@ -8,9 +8,12 @@ Server-side layout engine with WebSocket streaming for Geometra. Computes layout
8
8
  npm install @geometra/server
9
9
  ```
10
10
 
11
- ## Key Export
11
+ ## Key exports
12
12
 
13
- - `createServer` -- creates a Geometra layout server with WebSocket support
13
+ - `createServer` Geometra layout server with WebSocket streaming
14
+ - `shouldDeferClientSend` — backpressure helper (used by the server and tests)
15
+ - `onTransportMetrics` (option) — per-broadcast deferred send count, coalesced patch delta, binary outbound count
16
+ - Binary frame helpers (`encodeBinaryFrameJson`, …) — optional GEOM v1 JSON envelopes; see repo `PROTOCOL_COMPATIBILITY.md`
14
17
 
15
18
  ## Usage
16
19
 
@@ -0,0 +1,13 @@
1
+ import { Buffer } from 'node:buffer';
2
+ /** Magic prefix for optional v1 binary JSON envelopes (same JSON payload as text frames). */
3
+ export declare const BINARY_FRAME_MAGIC: Buffer<ArrayBuffer>;
4
+ /** True if buffer looks like a v1 binary envelope (GEOM + version 1). */
5
+ export declare function isBinaryFrameBuffer(data: Buffer | Uint8Array): boolean;
6
+ /** Wrap UTF-8 JSON bytes in a binary envelope for WebSocket binary frames. */
7
+ export declare function encodeBinaryFrameJson(jsonUtf8: string): Buffer;
8
+ /**
9
+ * Decode JSON string from a v1 binary envelope.
10
+ * Bytes after `header + payloadLength` are ignored so callers may pass a longer buffer.
11
+ */
12
+ export declare function decodeBinaryFrameJson(data: Buffer): string;
13
+ //# sourceMappingURL=binary-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-frame.d.ts","sourceRoot":"","sources":["../src/binary-frame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,qBAAwC,CAAA;AAKvE,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAStE;AAED,8EAA8E;AAC9E,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO9D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS1D"}
@@ -0,0 +1,39 @@
1
+ import { Buffer } from 'node:buffer';
2
+ /** Magic prefix for optional v1 binary JSON envelopes (same JSON payload as text frames). */
3
+ export const BINARY_FRAME_MAGIC = Buffer.from([0x47, 0x45, 0x4f, 0x4d]); // GEOM
4
+ const FRAME_VERSION = 1;
5
+ const HEADER_BYTES = 9;
6
+ /** True if buffer looks like a v1 binary envelope (GEOM + version 1). */
7
+ export function isBinaryFrameBuffer(data) {
8
+ if (data.length < HEADER_BYTES)
9
+ return false;
10
+ return (data[0] === 0x47 &&
11
+ data[1] === 0x45 &&
12
+ data[2] === 0x4f &&
13
+ data[3] === 0x4d &&
14
+ data[4] === FRAME_VERSION);
15
+ }
16
+ /** Wrap UTF-8 JSON bytes in a binary envelope for WebSocket binary frames. */
17
+ export function encodeBinaryFrameJson(jsonUtf8) {
18
+ const payload = Buffer.from(jsonUtf8, 'utf8');
19
+ const header = Buffer.alloc(HEADER_BYTES);
20
+ BINARY_FRAME_MAGIC.copy(header, 0);
21
+ header.writeUInt8(FRAME_VERSION, 4);
22
+ header.writeUInt32LE(payload.length, 5);
23
+ return Buffer.concat([header, payload]);
24
+ }
25
+ /**
26
+ * Decode JSON string from a v1 binary envelope.
27
+ * Bytes after `header + payloadLength` are ignored so callers may pass a longer buffer.
28
+ */
29
+ export function decodeBinaryFrameJson(data) {
30
+ if (!isBinaryFrameBuffer(data)) {
31
+ throw new Error('Not a GEOM binary frame');
32
+ }
33
+ const len = data.readUInt32LE(5);
34
+ if (HEADER_BYTES + len > data.length) {
35
+ throw new Error('Truncated binary frame payload');
36
+ }
37
+ return data.subarray(HEADER_BYTES, HEADER_BYTES + len).toString('utf8');
38
+ }
39
+ //# sourceMappingURL=binary-frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-frame.js","sourceRoot":"","sources":["../src/binary-frame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6FAA6F;AAC7F,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA,CAAC,OAAO;AAE/E,MAAM,aAAa,GAAG,CAAC,CAAA;AACvB,MAAM,YAAY,GAAG,CAAC,CAAA;AAEtB,yEAAyE;AACzE,MAAM,UAAU,mBAAmB,CAAC,IAAyB;IAC3D,IAAI,IAAI,CAAC,MAAM,GAAG,YAAY;QAAE,OAAO,KAAK,CAAA;IAC5C,OAAO,CACL,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;QAChB,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;QAChB,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;QAChB,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;QAChB,IAAI,CAAC,CAAC,CAAC,KAAK,aAAa,CAC1B,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACzC,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAClC,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC,CAAA;IACnC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IACvC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAC5C,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IAChC,IAAI,YAAY,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACzE,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { createServer } from './server.js';
2
- export type { TexturaServer, TexturaServerOptions } from './server.js';
3
- export { diffLayout } from './protocol.js';
1
+ export { createServer, shouldDeferClientSend } from './server.js';
2
+ export type { TexturaServer, TexturaServerOptions, ServerTransportMetrics } from './server.js';
3
+ export { diffLayout, CLOSE_AUTH_FAILED, CLOSE_FORBIDDEN } from './protocol.js';
4
4
  export type { ServerMessage, ClientMessage, LayoutPatch } from './protocol.js';
5
+ export { encodeBinaryFrameJson, decodeBinaryFrameJson, isBinaryFrameBuffer } from './binary-frame.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACjE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9E,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC9E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- export { createServer } from './server.js';
2
- export { diffLayout } from './protocol.js';
1
+ export { createServer, shouldDeferClientSend } from './server.js';
2
+ export { diffLayout, CLOSE_AUTH_FAILED, CLOSE_FORBIDDEN } from './protocol.js';
3
+ export { encodeBinaryFrameJson, decodeBinaryFrameJson, isBinaryFrameBuffer } from './binary-frame.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEjE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE9E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA"}
@@ -2,6 +2,10 @@ import type { ComputedLayout } from 'textura';
2
2
  import type { UIElement } from '@geometra/core';
3
3
  /** Increment when the wire message shape changes in a non-backward-compatible way. */
4
4
  export declare const PROTOCOL_VERSION = 1;
5
+ /** WebSocket close code: connection rejected by onConnection hook. */
6
+ export declare const CLOSE_AUTH_FAILED = 4001;
7
+ /** WebSocket close code: message rejected by onMessage hook. */
8
+ export declare const CLOSE_FORBIDDEN = 4003;
5
9
  /**
6
10
  * Protocol compatibility rule:
7
11
  * - undefined means legacy v1 and is accepted.
@@ -23,6 +27,7 @@ export type ServerMessage = (VersionedMessage & {
23
27
  }) | (VersionedMessage & {
24
28
  type: 'error';
25
29
  message: string;
30
+ code?: number;
26
31
  });
27
32
  /** Messages sent from client to server. */
28
33
  export type ClientMessage = (VersionedMessage & {
@@ -47,6 +52,10 @@ export type ClientMessage = (VersionedMessage & {
47
52
  type: 'resize';
48
53
  width: number;
49
54
  height: number;
55
+ /** Optional capability negotiation (v1+). */
56
+ capabilities?: {
57
+ binaryFraming?: boolean;
58
+ };
50
59
  });
51
60
  /** A patch describing a change to a single node's geometry. */
52
61
  export interface LayoutPatch {
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,IAAI,CAAA;AAEjC;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,cAAc,SAAmB,GAChC,OAAO,CAGT;AAED,UAAU,gBAAgB;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GACrB,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAC/E,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,GAC9D,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE3D,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GACrB,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC/E,CAAC,gBAAgB,GAAG;IAClB,IAAI,EAAE,KAAK,CAAA;IACX,SAAS,EAAE,WAAW,GAAG,SAAS,CAAA;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAC,GACF,CAAC,gBAAgB,GAAG;IAClB,IAAI,EAAE,aAAa,CAAA;IACnB,SAAS,EAAE,oBAAoB,GAAG,qBAAqB,GAAG,kBAAkB,CAAA;IAC5E,IAAI,EAAE,MAAM,CAAA;CACb,CAAC,GACF,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE1E,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAgBrE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CACxB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,cAAc,EACpB,IAAI,GAAE,MAAM,EAAO,GAClB,WAAW,EAAE,CAuBf"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,IAAI,CAAA;AAEjC,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC,gEAAgE;AAChE,eAAO,MAAM,eAAe,OAAO,CAAA;AAEnC;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,cAAc,SAAmB,GAChC,OAAO,CAGT;AAED,UAAU,gBAAgB;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GACrB,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAC/E,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,GAC9D,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAE1E,2CAA2C;AAC3C,MAAM,MAAM,aAAa,GACrB,CAAC,gBAAgB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC/E,CAAC,gBAAgB,GAAG;IAClB,IAAI,EAAE,KAAK,CAAA;IACX,SAAS,EAAE,WAAW,GAAG,SAAS,CAAA;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAC,GACF,CAAC,gBAAgB,GAAG;IAClB,IAAI,EAAE,aAAa,CAAA;IACnB,SAAS,EAAE,oBAAoB,GAAG,qBAAqB,GAAG,kBAAkB,CAAA;IAC5E,IAAI,EAAE,MAAM,CAAA;CACb,CAAC,GACF,CAAC,gBAAgB,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,YAAY,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC3C,CAAC,CAAA;AAEN,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAgBrE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CACxB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,cAAc,EACpB,IAAI,GAAE,MAAM,EAAO,GAClB,WAAW,EAAE,CAuBf"}
package/dist/protocol.js CHANGED
@@ -1,5 +1,9 @@
1
1
  /** Increment when the wire message shape changes in a non-backward-compatible way. */
2
2
  export const PROTOCOL_VERSION = 1;
3
+ /** WebSocket close code: connection rejected by onConnection hook. */
4
+ export const CLOSE_AUTH_FAILED = 4001;
5
+ /** WebSocket close code: message rejected by onMessage hook. */
6
+ export const CLOSE_FORBIDDEN = 4003;
3
7
  /**
4
8
  * Protocol compatibility rule:
5
9
  * - undefined means legacy v1 and is accepted.
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import type { IncomingMessage } from 'http';
1
2
  import type { UIElement } from '@geometra/core';
3
+ import type { ClientMessage } from './protocol.js';
2
4
  export interface TexturaServerOptions {
3
5
  /** Port to listen on. Default: 3100. */
4
6
  port?: number;
@@ -8,6 +10,33 @@ export interface TexturaServerOptions {
8
10
  height?: number;
9
11
  /** Called on errors during layout computation or broadcasting. */
10
12
  onError?: (error: unknown) => void;
13
+ /**
14
+ * Called when a new WebSocket client connects. Receives the HTTP upgrade
15
+ * request (headers, URL, etc.). Return any truthy value to accept the
16
+ * connection — that value becomes the connection context passed to
17
+ * `onMessage` and `onDisconnect`. Return `null` to reject (closes with
18
+ * 4001). Throwing also rejects. Async handlers are supported.
19
+ */
20
+ onConnection?: (request: IncomingMessage) => unknown | Promise<unknown>;
21
+ /** Called when an accepted client disconnects. Receives the connection context. */
22
+ onDisconnect?: (context: unknown) => void;
23
+ /**
24
+ * Called before processing each client message. Return `false` to reject
25
+ * the message (sends a 4003 error to the client, event is not dispatched).
26
+ */
27
+ onMessage?: (message: ClientMessage, context: unknown) => boolean;
28
+ /** Backpressure threshold; clients above this buffered amount are deferred. */
29
+ backpressureBytes?: number;
30
+ /** Per-broadcast transport telemetry (backpressure, coalescing, binary outbound). */
31
+ onTransportMetrics?: (metrics: ServerTransportMetrics) => void;
32
+ }
33
+ /** Emitted after each successful broadcast (not on early no-op returns). */
34
+ export interface ServerTransportMetrics {
35
+ deferredSends: number;
36
+ /** How many raw patches were merged by coalescing (0 when no previous layout). */
37
+ coalescedPatchDelta: number;
38
+ /** Count of clients that received a binary WebSocket frame this round. */
39
+ binaryOutboundFrames: number;
11
40
  }
12
41
  export interface TexturaServer {
13
42
  /** Trigger a re-render for all connected clients. */
@@ -15,6 +44,7 @@ export interface TexturaServer {
15
44
  /** Shut down the server. */
16
45
  close(): void;
17
46
  }
47
+ export declare function shouldDeferClientSend(bufferedAmount: number, backpressureBytes: number): boolean;
18
48
  /**
19
49
  * Create a Textura server that computes layout and streams geometry to clients.
20
50
  *
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,gBAAgB,CAAA;AAI9D,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,IAAI,IAAI,CAAA;IACd,4BAA4B;IAC5B,KAAK,IAAI,IAAI,CAAA;CACd;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,SAAS,EACrB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CA0IxB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAI3C,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,gBAAgB,CAAA;AAE9D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,eAAe,CAAA;AAGjE,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvE,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;IACjE,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAA;CAC/D;AAED,4EAA4E;AAC5E,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,mBAAmB,EAAE,MAAM,CAAA;IAC3B,0EAA0E;IAC1E,oBAAoB,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,IAAI,IAAI,CAAA;IACd,4BAA4B;IAC5B,KAAK,IAAI,IAAI,CAAA;CACd;AAED,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,SAAS,EACrB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CA2NxB"}
package/dist/server.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import { WebSocketServer } from 'ws';
2
2
  import { init, computeLayout } from 'textura';
3
3
  import { toLayoutTree, dispatchHit, dispatchKeyboardEvent, dispatchCompositionEvent } from '@geometra/core';
4
- import { diffLayout, PROTOCOL_VERSION, isProtocolCompatible } from './protocol.js';
4
+ import { diffLayout, coalescePatches, PROTOCOL_VERSION, isProtocolCompatible, CLOSE_AUTH_FAILED, CLOSE_FORBIDDEN } from './protocol.js';
5
+ import { encodeBinaryFrameJson } from './binary-frame.js';
6
+ export function shouldDeferClientSend(bufferedAmount, backpressureBytes) {
7
+ return bufferedAmount > backpressureBytes;
8
+ }
5
9
  /**
6
10
  * Create a Textura server that computes layout and streams geometry to clients.
7
11
  *
@@ -14,16 +18,24 @@ export async function createServer(view, options = {}) {
14
18
  let width = options.width ?? 800;
15
19
  let height = options.height ?? 600;
16
20
  const clients = new Set();
21
+ const needsResync = new Set();
22
+ /** Clients that negotiated optional binary JSON envelopes for server→client frames. */
23
+ const clientBinaryFraming = new Map();
24
+ const contexts = new Map();
17
25
  let prevLayout = null;
18
26
  let currentTree = null;
27
+ const backpressureBytes = Math.max(1024, options.backpressureBytes ?? 512 * 1024);
19
28
  function computeAndBroadcast() {
20
29
  try {
21
30
  currentTree = view();
22
31
  const layoutTree = toLayoutTree(currentTree);
23
32
  const layout = computeLayout(layoutTree, { width, height });
24
33
  let msg;
34
+ let coalescedPatchDelta = 0;
25
35
  if (prevLayout) {
26
- const patches = diffLayout(prevLayout, layout);
36
+ const rawPatches = diffLayout(prevLayout, layout);
37
+ const patches = coalescePatches(rawPatches);
38
+ coalescedPatchDelta = Math.max(0, rawPatches.length - patches.length);
27
39
  if (patches.length === 0)
28
40
  return;
29
41
  // If patches are more than half the tree, just send full frame
@@ -38,12 +50,35 @@ export async function createServer(view, options = {}) {
38
50
  msg = { type: 'frame', layout, tree: currentTree, protocolVersion: PROTOCOL_VERSION };
39
51
  }
40
52
  prevLayout = layout;
41
- const data = JSON.stringify(msg);
53
+ let deferredSends = 0;
54
+ let binaryOutboundFrames = 0;
42
55
  for (const client of clients) {
43
56
  if (client.readyState === client.OPEN) {
44
- client.send(data);
57
+ if (shouldDeferClientSend(client.bufferedAmount, backpressureBytes)) {
58
+ deferredSends++;
59
+ needsResync.add(client);
60
+ continue;
61
+ }
62
+ const clientMsg = needsResync.has(client)
63
+ ? { type: 'frame', layout, tree: currentTree, protocolVersion: PROTOCOL_VERSION }
64
+ : msg;
65
+ if (needsResync.has(client))
66
+ needsResync.delete(client);
67
+ const json = JSON.stringify(clientMsg);
68
+ if (clientBinaryFraming.get(client)) {
69
+ binaryOutboundFrames++;
70
+ client.send(encodeBinaryFrameJson(json), { binary: true });
71
+ }
72
+ else {
73
+ client.send(json);
74
+ }
45
75
  }
46
76
  }
77
+ options.onTransportMetrics?.({
78
+ deferredSends,
79
+ coalescedPatchDelta,
80
+ binaryOutboundFrames,
81
+ });
47
82
  }
48
83
  catch (err) {
49
84
  if (options.onError) {
@@ -63,9 +98,10 @@ export async function createServer(view, options = {}) {
63
98
  }
64
99
  }
65
100
  const wss = new WebSocketServer({ port });
66
- wss.on('connection', (ws) => {
101
+ function acceptConnection(ws) {
67
102
  clients.add(ws);
68
- // Send current state immediately
103
+ needsResync.delete(ws);
104
+ clientBinaryFraming.delete(ws);
69
105
  if (prevLayout && currentTree) {
70
106
  const msg = {
71
107
  type: 'frame',
@@ -73,7 +109,13 @@ export async function createServer(view, options = {}) {
73
109
  tree: currentTree,
74
110
  protocolVersion: PROTOCOL_VERSION,
75
111
  };
76
- ws.send(JSON.stringify(msg));
112
+ const json = JSON.stringify(msg);
113
+ if (clientBinaryFraming.get(ws)) {
114
+ ws.send(encodeBinaryFrameJson(json), { binary: true });
115
+ }
116
+ else {
117
+ ws.send(json);
118
+ }
77
119
  }
78
120
  else {
79
121
  computeAndBroadcast();
@@ -90,9 +132,21 @@ export async function createServer(view, options = {}) {
90
132
  ws.send(JSON.stringify(errorMsg));
91
133
  return;
92
134
  }
135
+ if (options.onMessage) {
136
+ const ctx = contexts.get(ws);
137
+ if (!options.onMessage(msg, ctx)) {
138
+ const errorMsg = {
139
+ type: 'error',
140
+ message: 'Forbidden',
141
+ code: CLOSE_FORBIDDEN,
142
+ protocolVersion: PROTOCOL_VERSION,
143
+ };
144
+ ws.send(JSON.stringify(errorMsg));
145
+ return;
146
+ }
147
+ }
93
148
  if (msg.type === 'event' && currentTree && prevLayout) {
94
149
  dispatchHit(currentTree, prevLayout, msg.eventType, msg.x, msg.y);
95
- // After event handling, signals may have changed — re-render
96
150
  computeAndBroadcast();
97
151
  }
98
152
  else if (msg.type === 'key' && currentTree && prevLayout) {
@@ -113,6 +167,9 @@ export async function createServer(view, options = {}) {
113
167
  computeAndBroadcast();
114
168
  }
115
169
  else if (msg.type === 'resize') {
170
+ if (msg.capabilities?.binaryFraming) {
171
+ clientBinaryFraming.set(ws, true);
172
+ }
116
173
  width = Math.max(1, msg.width);
117
174
  height = Math.max(1, msg.height);
118
175
  prevLayout = null;
@@ -124,8 +181,35 @@ export async function createServer(view, options = {}) {
124
181
  }
125
182
  });
126
183
  ws.on('close', () => {
184
+ const ctx = contexts.get(ws);
127
185
  clients.delete(ws);
186
+ needsResync.delete(ws);
187
+ clientBinaryFraming.delete(ws);
188
+ contexts.delete(ws);
189
+ if (options.onDisconnect && ctx !== undefined) {
190
+ options.onDisconnect(ctx);
191
+ }
128
192
  });
193
+ }
194
+ wss.on('connection', (ws, request) => {
195
+ if (options.onConnection) {
196
+ Promise.resolve()
197
+ .then(() => options.onConnection(request))
198
+ .then((ctx) => {
199
+ if (ctx == null) {
200
+ ws.close(CLOSE_AUTH_FAILED, 'Authentication failed');
201
+ return;
202
+ }
203
+ contexts.set(ws, ctx);
204
+ acceptConnection(ws);
205
+ })
206
+ .catch(() => {
207
+ ws.close(CLOSE_AUTH_FAILED, 'Authentication failed');
208
+ });
209
+ }
210
+ else {
211
+ acceptConnection(ws);
212
+ }
129
213
  });
130
214
  // Initial render
131
215
  computeAndBroadcast();
@@ -136,6 +220,9 @@ export async function createServer(view, options = {}) {
136
220
  close() {
137
221
  wss.close();
138
222
  clients.clear();
223
+ needsResync.clear();
224
+ clientBinaryFraming.clear();
225
+ contexts.clear();
139
226
  },
140
227
  };
141
228
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geometra/server",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Server-side layout engine with WebSocket geometry streaming",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "check": "tsc --noEmit"
34
34
  },
35
35
  "dependencies": {
36
- "@geometra/core": "^1.0.0",
36
+ "@geometra/core": "^1.0.1",
37
37
  "ws": "^8.18.0"
38
38
  },
39
39
  "devDependencies": {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=protocol-compat.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-compat.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/protocol-compat.test.ts"],"names":[],"mappings":""}
@@ -1,77 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import WebSocket from 'ws';
3
- import { box } from '@geometra/core';
4
- import { createServer } from '../server.js';
5
- function pickPort() {
6
- return 39000 + Math.floor(Math.random() * 2000);
7
- }
8
- describe('protocol compatibility', () => {
9
- it('accepts unversioned client messages as v1-compatible', async () => {
10
- const port = pickPort();
11
- const server = await createServer(() => box({ width: 40, height: 20 }, []), { port, width: 200, height: 100 });
12
- await new Promise((resolve, reject) => {
13
- const ws = new WebSocket(`ws://127.0.0.1:${port}`);
14
- let sentResize = false;
15
- const timeout = setTimeout(() => reject(new Error('timed out waiting for unversioned compatibility response')), 5000);
16
- ws.on('message', (raw) => {
17
- const msg = JSON.parse(String(raw));
18
- if (msg.type === 'frame') {
19
- if (!sentResize) {
20
- // Unversioned resize should be accepted and trigger a new frame/patch without error.
21
- ws.send(JSON.stringify({ type: 'resize', width: 180, height: 120 }));
22
- sentResize = true;
23
- return;
24
- }
25
- clearTimeout(timeout);
26
- ws.close();
27
- resolve();
28
- return;
29
- }
30
- if (msg.type === 'error') {
31
- clearTimeout(timeout);
32
- reject(new Error(`unexpected protocol error: ${msg.message ?? 'unknown'}`));
33
- return;
34
- }
35
- });
36
- ws.on('error', (err) => {
37
- clearTimeout(timeout);
38
- reject(err);
39
- });
40
- }).finally(() => {
41
- server.close();
42
- });
43
- });
44
- it('returns explicit error for newer client protocol versions', async () => {
45
- const port = pickPort();
46
- const server = await createServer(() => box({ width: 40, height: 20 }, []), { port, width: 200, height: 100 });
47
- await new Promise((resolve, reject) => {
48
- const ws = new WebSocket(`ws://127.0.0.1:${port}`);
49
- const timeout = setTimeout(() => reject(new Error('timed out waiting for protocol mismatch error')), 5000);
50
- ws.on('open', () => {
51
- ws.send(JSON.stringify({
52
- type: 'event',
53
- eventType: 'onClick',
54
- x: 1,
55
- y: 1,
56
- protocolVersion: 999,
57
- }));
58
- });
59
- ws.on('message', (raw) => {
60
- const msg = JSON.parse(String(raw));
61
- if (msg.type !== 'error')
62
- return;
63
- clearTimeout(timeout);
64
- expect(msg.message).toContain('newer than server protocol');
65
- ws.close();
66
- resolve();
67
- });
68
- ws.on('error', (err) => {
69
- clearTimeout(timeout);
70
- reject(err);
71
- });
72
- }).finally(() => {
73
- server.close();
74
- });
75
- });
76
- });
77
- //# sourceMappingURL=protocol-compat.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-compat.test.js","sourceRoot":"","sources":["../../src/__tests__/protocol-compat.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=protocol-diff.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-diff.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/protocol-diff.test.ts"],"names":[],"mappings":""}
@@ -1,91 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { diffLayout, isProtocolCompatible, coalescePatches } from '../protocol.js';
3
- function cloneLayout(layout) {
4
- return {
5
- x: layout.x,
6
- y: layout.y,
7
- width: layout.width,
8
- height: layout.height,
9
- children: layout.children.map(cloneLayout),
10
- };
11
- }
12
- function applyPatches(layout, patches) {
13
- for (const patch of patches) {
14
- let node = layout;
15
- for (const idx of patch.path) {
16
- node = node.children[idx];
17
- }
18
- if (patch.x !== undefined)
19
- node.x = patch.x;
20
- if (patch.y !== undefined)
21
- node.y = patch.y;
22
- if (patch.width !== undefined)
23
- node.width = patch.width;
24
- if (patch.height !== undefined)
25
- node.height = patch.height;
26
- }
27
- }
28
- function makeBaseLayout() {
29
- return {
30
- x: 0,
31
- y: 0,
32
- width: 400,
33
- height: 300,
34
- children: [
35
- { x: 10, y: 20, width: 100, height: 40, children: [] },
36
- {
37
- x: 40,
38
- y: 80,
39
- width: 200,
40
- height: 120,
41
- children: [
42
- { x: 0, y: 0, width: 80, height: 20, children: [] },
43
- { x: 0, y: 28, width: 80, height: 20, children: [] },
44
- ],
45
- },
46
- ],
47
- };
48
- }
49
- describe('diffLayout', () => {
50
- it('applies patches to reach next layout during burst updates', () => {
51
- let prev = makeBaseLayout();
52
- for (let i = 1; i <= 30; i++) {
53
- const next = cloneLayout(prev);
54
- next.width += i;
55
- next.children[0].x += 1;
56
- next.children[1].children[1].y += 2;
57
- next.children[1].height += 1;
58
- const patches = diffLayout(prev, next);
59
- const patched = cloneLayout(prev);
60
- applyPatches(patched, patches);
61
- expect(patched).toEqual(next);
62
- prev = next;
63
- }
64
- });
65
- it('returns no patches when geometry is unchanged', () => {
66
- const base = makeBaseLayout();
67
- const same = cloneLayout(base);
68
- const patches = diffLayout(base, same);
69
- expect(patches).toEqual([]);
70
- });
71
- it('treats undefined/older protocol versions as compatible and newer as incompatible', () => {
72
- expect(isProtocolCompatible(undefined, 1)).toBe(true);
73
- expect(isProtocolCompatible(1, 1)).toBe(true);
74
- expect(isProtocolCompatible(0, 1)).toBe(true);
75
- expect(isProtocolCompatible(2, 1)).toBe(false);
76
- });
77
- it('coalesces repeated path updates with last-write wins semantics', () => {
78
- const merged = coalescePatches([
79
- { path: [1, 2], x: 10 },
80
- { path: [1, 2], y: 20 },
81
- { path: [1, 2], x: 30, width: 40 },
82
- { path: [3], height: 9 },
83
- { path: [3], y: 5 },
84
- ]);
85
- expect(merged).toEqual([
86
- { path: [1, 2], x: 30, y: 20, width: 40 },
87
- { path: [3], height: 9, y: 5 },
88
- ]);
89
- });
90
- });
91
- //# sourceMappingURL=protocol-diff.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-diff.test.js","sourceRoot":"","sources":["../../src/__tests__/protocol-diff.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=protocol-fixture.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-fixture.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/protocol-fixture.test.ts"],"names":[],"mappings":""}
@@ -1,20 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { readFileSync } from 'node:fs';
3
- import { isProtocolCompatible } from '../protocol.js';
4
- function readJSON(path) {
5
- return JSON.parse(readFileSync(path, 'utf8'));
6
- }
7
- describe('server protocol fixtures', () => {
8
- it('accepts shared v1 fixture shapes', () => {
9
- const frame = readJSON(new URL('../../../../fixtures/protocol/v1/frame.json', import.meta.url).pathname);
10
- const patch = readJSON(new URL('../../../../fixtures/protocol/v1/patch.json', import.meta.url).pathname);
11
- const error = readJSON(new URL('../../../../fixtures/protocol/v1/error.json', import.meta.url).pathname);
12
- expect(frame.type).toBe('frame');
13
- expect(patch.type).toBe('patch');
14
- expect(error.type).toBe('error');
15
- expect(isProtocolCompatible(frame.protocolVersion, 1)).toBe(true);
16
- expect(isProtocolCompatible(patch.protocolVersion, 1)).toBe(true);
17
- expect(isProtocolCompatible(error.protocolVersion, 1)).toBe(true);
18
- });
19
- });
20
- //# sourceMappingURL=protocol-fixture.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-fixture.test.js","sourceRoot":"","sources":["../../src/__tests__/protocol-fixture.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;AAC/C,CAAC;AAED,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,GAAG,CAAC,6CAA6C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAA;QACxG,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,GAAG,CAAC,6CAA6C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAA;QACxG,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,GAAG,CAAC,6CAA6C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAA;QAExG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAChC,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjE,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjE,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=protocol-perf-smoke.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-perf-smoke.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/protocol-perf-smoke.test.ts"],"names":[],"mappings":""}
@@ -1,68 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { diffLayout } from '../protocol.js';
3
- function nowMs() {
4
- return performance.now();
5
- }
6
- function makeTree(depth, breadth, seed = 0) {
7
- const node = {
8
- x: seed % 17,
9
- y: seed % 13,
10
- width: 100 + (seed % 29),
11
- height: 40 + (seed % 19),
12
- children: [],
13
- };
14
- if (depth === 0)
15
- return node;
16
- for (let i = 0; i < breadth; i++) {
17
- node.children.push(makeTree(depth - 1, breadth, seed + i + 1));
18
- }
19
- return node;
20
- }
21
- function mutateLayout(node) {
22
- return {
23
- x: node.x + 1,
24
- y: node.y + 2,
25
- width: node.width + 1,
26
- height: node.height + 1,
27
- children: node.children.map((c, i) => (i % 2 === 0
28
- ? mutateLayout(c)
29
- : { ...c, x: c.x + 1, children: c.children.map(gc => ({ ...gc })) })),
30
- };
31
- }
32
- function mutateLayoutWorstCase(node) {
33
- return {
34
- x: node.x + 2,
35
- y: node.y + 2,
36
- width: node.width + 2,
37
- height: node.height + 2,
38
- children: node.children.map(mutateLayoutWorstCase),
39
- };
40
- }
41
- describe('protocol perf smoke', () => {
42
- it('diffLayout handles rapid geometry bursts', () => {
43
- let prev = makeTree(4, 4);
44
- const start = nowMs();
45
- for (let i = 0; i < 60; i++) {
46
- const next = mutateLayout(prev);
47
- const patches = diffLayout(prev, next);
48
- expect(patches.length).toBeGreaterThan(0);
49
- prev = next;
50
- }
51
- const elapsed = nowMs() - start;
52
- expect(elapsed).toBeGreaterThan(0);
53
- expect(elapsed).toBeLessThanOrEqual(500);
54
- });
55
- it('handles large tree worst-case churn within smoke threshold', () => {
56
- let prev = makeTree(5, 4);
57
- const start = nowMs();
58
- for (let i = 0; i < 12; i++) {
59
- const next = mutateLayoutWorstCase(prev);
60
- const patches = diffLayout(prev, next);
61
- expect(patches.length).toBeGreaterThan(100);
62
- prev = next;
63
- }
64
- const elapsed = nowMs() - start;
65
- expect(elapsed).toBeLessThanOrEqual(900);
66
- });
67
- });
68
- //# sourceMappingURL=protocol-perf-smoke.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"protocol-perf-smoke.test.js","sourceRoot":"","sources":["../../src/__tests__/protocol-perf-smoke.test.ts"],"names":[],"mappings":"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"}