wenay-common2 1.0.69 → 1.0.73

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 (89) hide show
  1. package/README.md +11 -1
  2. package/demo/client.ts +222 -0
  3. package/demo/index.html +58 -0
  4. package/demo/server.ts +93 -0
  5. package/doc/INTENT.md +31 -0
  6. package/doc/ROADMAP.md +19 -1
  7. package/doc/changes/1.0.70.md +4 -0
  8. package/doc/changes/1.0.71.md +9 -0
  9. package/doc/changes/1.0.72.md +6 -0
  10. package/doc/changes/1.0.73.md +6 -0
  11. package/doc/wenay-common2-rare.md +29 -8
  12. package/doc/wenay-common2.md +13 -2
  13. package/lib/Common/media/media-index.d.ts +1 -0
  14. package/lib/Common/media/media-index.js +1 -0
  15. package/lib/Common/media/media-source.d.ts +3 -0
  16. package/lib/Common/media/media-source.js +136 -27
  17. package/lib/Common/media/media-view.d.ts +42 -0
  18. package/lib/Common/media/media-view.js +191 -0
  19. package/lib/Common/peer/peer-client.d.ts +12 -5
  20. package/lib/Common/peer/peer-client.js +53 -3
  21. package/lib/Common/peer/peer-host.d.ts +8 -4
  22. package/lib/Common/peer/peer-host.js +2 -2
  23. package/lib/Common/peer/peer-relay.d.ts +10 -2
  24. package/lib/Common/peer/peer-relay.js +40 -18
  25. package/observe/PLAN.md +131 -0
  26. package/observe/README.md +226 -0
  27. package/observe/hot-write.test.ts +95 -0
  28. package/observe/listen-core.test.ts +66 -0
  29. package/observe/listen-store.test.ts +56 -0
  30. package/observe/listen.test.ts +92 -0
  31. package/observe/reactive.test.ts +270 -0
  32. package/observe/reactive.ts +1 -0
  33. package/observe/store-manager.test.ts +118 -0
  34. package/observe/store-mirror.example.ts +74 -0
  35. package/observe/store.test.ts +235 -0
  36. package/observe/store.ts +1 -0
  37. package/observe/usage-real-socket.ts +174 -0
  38. package/observe/usage.ts +200 -0
  39. package/oracle/PASSED.md +27 -0
  40. package/oracle/README.md +12 -0
  41. package/oracle/fixes-primitives.spec.ts +90 -0
  42. package/oracle/realsocket/_rs.ts +106 -0
  43. package/oracle/realsocket/auth.spec.ts +91 -0
  44. package/oracle/realsocket/callbacks.spec.ts +122 -0
  45. package/oracle/realsocket/caps.spec.ts +124 -0
  46. package/oracle/realsocket/core.spec.ts +49 -0
  47. package/oracle/realsocket/dedupe.spec.ts +142 -0
  48. package/oracle/realsocket/errors.spec.ts +121 -0
  49. package/oracle/realsocket/lifecycle.spec.ts +128 -0
  50. package/oracle/realsocket/limits.spec.ts +98 -0
  51. package/oracle/realsocket/pipe.spec.ts +101 -0
  52. package/oracle/realsocket/shape.spec.ts +124 -0
  53. package/oracle/realsocket/slimv2.spec.ts +116 -0
  54. package/oracle/realsocket/stress.spec.ts +132 -0
  55. package/oracle/regression/_clientapiall-replay.fixture.ts +57 -0
  56. package/oracle/regression/async-queues.spec.ts +254 -0
  57. package/oracle/regression/bytestream.spec.ts +152 -0
  58. package/oracle/regression/clientapiall-replay-types.spec.ts +47 -0
  59. package/oracle/regression/core-clone-equal.spec.ts +124 -0
  60. package/oracle/regression/data-structures.spec.ts +135 -0
  61. package/oracle/regression/listen-events.spec.ts +206 -0
  62. package/oracle/regression/observe-core.spec.ts +278 -0
  63. package/oracle/regression/package-export.spec.ts +120 -0
  64. package/oracle/regression/rpc-dedupe-callbacks.spec.ts +195 -0
  65. package/oracle/regression/rpc-lifecycle.spec.ts +150 -0
  66. package/oracle/regression/store-each.spec.ts +209 -0
  67. package/package.json +6 -1
  68. package/replay/PLAN.md +171 -0
  69. package/replay/canvas-socket.test.ts +187 -0
  70. package/replay/coalesce.test.ts +260 -0
  71. package/replay/conflate-socket.test.ts +288 -0
  72. package/replay/conflate.test.ts +225 -0
  73. package/replay/history.test.ts +222 -0
  74. package/replay/media-socket.test.ts +157 -0
  75. package/replay/media-view.test.ts +164 -0
  76. package/replay/offline-store-socket.test.ts +290 -0
  77. package/replay/offline-store.test.ts +156 -0
  78. package/replay/peer-repair.test.ts +184 -0
  79. package/replay/peer-sdk.test.ts +228 -0
  80. package/replay/replay-listen.test.ts +156 -0
  81. package/replay/route-coordinator.test.ts +314 -0
  82. package/replay/route-handoff.test.ts +150 -0
  83. package/replay/route-webrtc.test.ts +321 -0
  84. package/replay/rpc-auto.test.ts +256 -0
  85. package/replay/socket-replay.test.ts +199 -0
  86. package/replay/staleness.test.ts +176 -0
  87. package/replay/store-replay.test.ts +151 -0
  88. package/replay/video-socket.demo.ts +200 -0
  89. package/doc/changes/1.0.63.md +0 -8
@@ -0,0 +1,200 @@
1
+ // ============================================================
2
+ // replay/video-socket.demo.ts
3
+ //
4
+ // «Видео» через replay-линию по НАСТОЯЩЕМУ Socket.IO:
5
+ // сервер стримит анимацию (мячик в ASCII-кадре) как store-патчи.
6
+ // Кадр = состояние стора; дельта-кадр (P-frame) = патчи строк;
7
+ // keyframe (I-frame) = снапшот. Ровно та же механика, что у
8
+ // маркет-даты/зеркала стора — payload-agnostic.
9
+ //
10
+ // - зритель A смотрит с начала (все дельты)
11
+ // - зритель B приходит к СЕРЕДИНЕ фильма: получает keyframe
12
+ // текущей картинки + live, а НЕ бэклог прошедших кадров
13
+ // - зритель B «лагает» (обрыв на несколько кадров): реконнект
14
+ // через since → только пропущенный хвост дельт
15
+ //
16
+ // Запуск: npx ts-node replay/video-socket.demo.ts
17
+ // ============================================================
18
+
19
+ import express from 'express'
20
+ import {createServer} from 'http'
21
+ import type {AddressInfo} from 'net'
22
+ import {Server as SocketIOServer} from 'socket.io'
23
+ import {io} from 'socket.io-client'
24
+ import {listen as createListenPair} from '../src/Common/events/Listen'
25
+ import {createRpcClientHub} from '../src/Common/rcp/rpc-clientHub'
26
+ import {createRpcServerAuto} from '../src/Common/rcp/rpc-server-auto'
27
+ import {createStore, StorePatch} from '../src/Common/Observe/store'
28
+ import {flushReactive} from '../src/Common/Observe/reactive'
29
+ import {exposeStoreReplay, syncStoreReplay} from '../src/Common/Observe/store-replay'
30
+ import {ReplayRemote} from '../src/Common/events/replay-index'
31
+
32
+ const W = 32, H = 10, FRAMES = 60
33
+ const delay = (ms: number) => new Promise<void>(r => setTimeout(r, ms))
34
+ const json = (v: any) => JSON.stringify(v)
35
+
36
+ type Video = {
37
+ tick: number
38
+ rows: Record<number, string>
39
+ }
40
+
41
+ function assert(cond: any, message: string) {
42
+ if (!cond) throw new Error(`FAIL: ${message}`)
43
+ console.log(' OK ', message)
44
+ }
45
+
46
+ // ============ «кодек»: мячик по кадру ============
47
+ function renderRow(y: number, bx: number, by: number) {
48
+ let row = ''
49
+ for (let x = 0; x < W; x++) row += (x == bx && y == by) ? 'O' : '·'
50
+ return row
51
+ }
52
+
53
+ function paintFrame(video: Video, bx: number, by: number, prevBy: number) {
54
+ // дельта-кадр: перерисовываем только изменившиеся строки (P-frame)
55
+ video.rows[prevBy] = renderRow(prevBy, -1, -1)
56
+ video.rows[by] = renderRow(by, bx, by)
57
+ video.tick++
58
+ }
59
+
60
+ function printFrame(title: string, video: Video) {
61
+ console.log(`\n ${title} (tick ${video.tick})`)
62
+ for (let y = 0; y < H; y++) console.log(' ' + (video.rows[y] ?? ''.padEnd(W, '?')))
63
+ }
64
+
65
+ // ============ провод ============
66
+ async function startRealServer(object: object) {
67
+ const app = express()
68
+ const httpServer = createServer(app)
69
+ const ioServer = new SocketIOServer(httpServer, {maxHttpBufferSize: 1e8})
70
+ ioServer.on('connection', socket => {
71
+ const [disconnect, disconnectListen] = createListenPair<[]>()
72
+ socket.on('disconnect', () => disconnect())
73
+ createRpcServerAuto({
74
+ socket: {
75
+ emit: (key, data) => socket.emit(key, data),
76
+ on: (key, cb) => socket.on(key, cb),
77
+ },
78
+ socketKey: 'video',
79
+ object,
80
+ disconnectListen,
81
+ })
82
+ })
83
+ await new Promise<void>((resolve, reject) => {
84
+ httpServer.once('error', reject)
85
+ httpServer.listen(0, '127.0.0.1', resolve)
86
+ })
87
+ const port = (httpServer.address() as AddressInfo).port
88
+ return {
89
+ port,
90
+ close: () => new Promise<void>(resolve => {
91
+ ioServer.close()
92
+ httpServer.close(() => resolve())
93
+ }),
94
+ }
95
+ }
96
+
97
+ async function connectViewer(port: number, name: string) {
98
+ const hub = createRpcClientHub(
99
+ () => io(`http://127.0.0.1:${port}`, {transports: ['websocket'], forceNew: true}),
100
+ r => ({api: r<any>('video')}) as const,
101
+ )
102
+ const clients = await hub.setToken(null)
103
+ await clients.api.readyStrict()
104
+ const deep = clients.api.func as any
105
+ const remote: ReplayRemote<[StorePatch]> = {
106
+ line: deep.replay.line,
107
+ since: (s: number) => deep.replay.since(s),
108
+ keyframe: () => deep.replay.keyframe(),
109
+ }
110
+ console.log(` [${name}] connected`)
111
+ return {remote, close: () => hub.socket?.disconnect?.()}
112
+ }
113
+
114
+ async function main() {
115
+ console.log('\n[video] bouncing ball over a real Socket.IO wire')
116
+
117
+ // ============ сервер: «съёмка» ============
118
+ const film = createStore<Video>({tick: 0, rows: Object.fromEntries(
119
+ Array.from({length: H}, (_, y) => [y, renderRow(y, 2, 3)]),
120
+ )})
121
+ const exposed = exposeStoreReplay(film, {history: 40}) // журнал ~13 кадров (3 патча/кадр)
122
+ const stats = {keyframe: 0, since: 0}
123
+ const facade = {
124
+ ...exposed.api,
125
+ replay: {
126
+ ...exposed.api.replay,
127
+ since: (s: number) => { stats.since++; return exposed.api.replay.since(s) },
128
+ keyframe: () => { stats.keyframe++; return exposed.api.replay.keyframe() },
129
+ },
130
+ }
131
+ const server = await startRealServer(facade)
132
+
133
+ let bx = 2, by = 3, dx = 1, dy = 1
134
+ async function playFrames(n: number) {
135
+ for (let i = 0; i < n; i++) {
136
+ const prevBy = by
137
+ bx += dx; by += dy
138
+ if (bx <= 0 || bx >= W - 1) dx = -dx
139
+ if (by <= 0 || by >= H - 1) dy = -dy
140
+ paintFrame(film.state, bx, by, prevBy)
141
+ await flushReactive(film.state)
142
+ await delay(15) // ~66 fps
143
+ }
144
+ }
145
+
146
+ const closers: (() => void)[] = []
147
+ try {
148
+ // ============ зритель A: с первого кадра ============
149
+ const a = await connectViewer(server.port, 'viewer A')
150
+ closers.push(a.close)
151
+ const screenA = createStore<Video>({tick: -1, rows: {}})
152
+ let seqA = -1
153
+ const subA = syncStoreReplay(screenA, a.remote, {onSeq: s => seqA = s})
154
+ await subA.ready
155
+
156
+ await playFrames(25)
157
+ await delay(60)
158
+ assert(json(screenA.state) == json(film.snapshot()), 'viewer A is frame-perfect after 25 frames')
159
+
160
+ // ============ зритель B: опоздал к середине фильма ============
161
+ const b = await connectViewer(server.port, 'viewer B (late)')
162
+ closers.push(b.close)
163
+ const screenB = createStore<Video>({tick: -1, rows: {}})
164
+ let seqB = -1
165
+ let framesTraveledToB = 0
166
+ const subB = syncStoreReplay(screenB, b.remote, {onSeq: s => { seqB = s; framesTraveledToB++ }})
167
+ await subB.ready
168
+ assert(json(screenB.state) == json(film.snapshot()), 'late viewer B got the CURRENT picture instantly')
169
+ assert(framesTraveledToB == 1, `keyframe, not a 25-frame backlog (deliveries: ${framesTraveledToB})`)
170
+ printFrame('viewer B first sees', screenB.state)
171
+
172
+ // ============ зритель B лагает: короткий обрыв → хвост дельт ============
173
+ subB()
174
+ const kfBefore = stats.keyframe
175
+ await playFrames(4) // 12 патчей мимо B — влезают в журнал (40)
176
+ let lagDeliveries = 0
177
+ const subB2 = syncStoreReplay(screenB, b.remote, {since: seqB, onSeq: s => { seqB = s; lagDeliveries++ }})
178
+ await subB2.ready
179
+ assert(json(screenB.state) == json(film.snapshot()), 'lagging viewer B caught up via delta tail')
180
+ assert(stats.keyframe == kfBefore, `no keyframe needed for a short lag (tail of ${lagDeliveries} patches)`)
181
+
182
+ // ============ финал: все смотрят одно и то же ============
183
+ await playFrames(FRAMES - 29)
184
+ await delay(80)
185
+ assert(json(screenA.state) == json(film.snapshot()), 'final frame: viewer A pixel-perfect')
186
+ assert(json(screenB.state) == json(film.snapshot()), 'final frame: viewer B pixel-perfect')
187
+ printFrame('final frame, both screens', screenB.state)
188
+ console.log(`\n server stats: keyframes served = ${stats.keyframe}, tail requests = ${stats.since}`)
189
+ console.log('\nALL OK: video over replay line passed')
190
+ subA()
191
+ subB2()
192
+ } finally {
193
+ for (const close of closers) close()
194
+ await delay(20)
195
+ exposed.close()
196
+ await server.close()
197
+ }
198
+ }
199
+
200
+ main().catch(e => { console.error(e); process.exit(1) })
@@ -1,8 +0,0 @@
1
- # 1.0.63
2
-
3
- - Add `Observe.createStoreManager` and `Observe.managedStore` resource builders for declarative mirror/replay/offline store startup.
4
- - Add manager planning with priority, tags, usage scoring, `large`, and `explicitOnly` gates.
5
- - Add manager lifecycle helpers: `start`, `startPlanned`, `stop`, `stopAll`, `get`, `touch`, `usage`, `statusListen`, and typed `handles`.
6
- - Rename public-facing Observe oracle folder from `observable2` to `observe` and make `Listen.ts` / `reactive.ts` the canonical source files, leaving numbered files as compatibility shims.
7
- - Update brief and extended API docs for the store manager and current naming.
8
- - Publish package `wenay-common2@1.0.63`.