@weft-js/start 0.1.1 → 0.1.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 (213) hide show
  1. package/README.md +36 -3
  2. package/crate/Cargo.lock +1347 -0
  3. package/crate/Cargo.toml +72 -0
  4. package/crate/rust-toolchain.toml +3 -0
  5. package/crate/src/build/emit.rs +103 -0
  6. package/crate/src/build.rs +367 -0
  7. package/crate/src/ctx.rs +186 -0
  8. package/crate/src/envelope.rs +163 -0
  9. package/crate/src/functions.rs +173 -0
  10. package/crate/src/lib.rs +118 -0
  11. package/crate/src/native.rs +212 -0
  12. package/crate/src/otel.rs +62 -0
  13. package/crate/src/server.rs +203 -0
  14. package/package.json +46 -37
  15. package/src/app.mjs +38 -8
  16. package/src/asset-receipt.d.mts +13 -0
  17. package/src/asset-receipt.mjs +61 -0
  18. package/src/auth/better-auth.ts +215 -17
  19. package/src/auth/drizzle.ts +1 -1
  20. package/src/auth.ts +21 -6
  21. package/src/boot.ts +19 -8
  22. package/src/bus/postgres.ts +32 -19
  23. package/src/bus.ts +7 -4
  24. package/src/{capture.mjs → capture.ts} +34 -1
  25. package/src/changes-sql.d.mts +8 -2
  26. package/src/changes-sql.mjs +26 -10
  27. package/src/changes.ts +165 -66
  28. package/src/channel/connections.ts +216 -0
  29. package/src/channel/socket.ts +396 -0
  30. package/src/channel-fake.ts +19 -4
  31. package/src/channel.ts +120 -492
  32. package/src/client.tsx +9 -2
  33. package/src/contract/describe.mjs +231 -0
  34. package/src/contract.mjs +144 -157
  35. package/src/data/cache.ts +354 -0
  36. package/src/data/definitions.ts +94 -0
  37. package/src/data/draft.ts +104 -0
  38. package/src/data/foundation.ts +95 -0
  39. package/src/data/live-api.ts +22 -0
  40. package/src/data/mutation.ts +138 -0
  41. package/src/data/primitives.ts +135 -0
  42. package/src/data/query.ts +106 -0
  43. package/src/data/transport.ts +121 -0
  44. package/src/data/types.ts +303 -0
  45. package/src/data.ts +69 -894
  46. package/src/db/bun-sqlite.d.ts +4 -1
  47. package/src/db/changes-mysql.ts +61 -21
  48. package/src/db/changes-postgres.ts +197 -118
  49. package/src/db/changes-sqlite.ts +131 -64
  50. package/src/db/core.ts +38 -13
  51. package/src/db/index.ts +20 -4
  52. package/src/db/mysql.ts +22 -6
  53. package/src/db/postgres.ts +49 -13
  54. package/src/db/sqlite.ts +53 -27
  55. package/src/derive.d.mts +4 -1
  56. package/src/derive.mjs +136 -39
  57. package/src/dev-runtime.mjs +8 -4
  58. package/src/exec/errors.ts +57 -0
  59. package/src/exec/landing.ts +66 -0
  60. package/src/exec/scope.ts +174 -0
  61. package/src/exec/touch.ts +80 -0
  62. package/src/exec/validation.ts +176 -0
  63. package/src/exec.ts +191 -341
  64. package/src/extension.ts +8 -2
  65. package/src/first-paint.ts +26 -9
  66. package/src/form/value.ts +15 -0
  67. package/src/form.tsx +132 -43
  68. package/src/hub/channels.ts +102 -0
  69. package/src/hub/connections.ts +147 -0
  70. package/src/hub/delivery.ts +62 -0
  71. package/src/hub/feed.ts +168 -0
  72. package/src/hub/presence.ts +64 -0
  73. package/src/hub/route.ts +158 -0
  74. package/src/hub/subscriptions.ts +212 -0
  75. package/src/hub/topic.ts +98 -0
  76. package/src/hub/tracked.ts +12 -0
  77. package/src/hub/types.ts +190 -0
  78. package/src/hub/view.ts +147 -0
  79. package/src/hub.ts +132 -758
  80. package/src/index.ts +84 -10
  81. package/src/inline.mjs +356 -0
  82. package/src/inspect/compare.mjs +162 -0
  83. package/src/inspect/routes.mjs +154 -0
  84. package/src/inspect/samples.mjs +59 -0
  85. package/src/inspect/url.mjs +2 -0
  86. package/src/inspect.mjs +146 -187
  87. package/src/link/election.ts +178 -0
  88. package/src/link/lines.ts +27 -0
  89. package/src/link.ts +90 -166
  90. package/src/list-drizzle.ts +104 -18
  91. package/src/list.ts +127 -36
  92. package/src/listen.ts +32 -6
  93. package/src/live/api.ts +18 -0
  94. package/src/live/backoff.ts +6 -0
  95. package/src/live/frames.ts +42 -0
  96. package/src/live/read.ts +77 -0
  97. package/src/live/sub-types.ts +24 -0
  98. package/src/live/transport.ts +86 -0
  99. package/src/live/types.ts +52 -0
  100. package/src/live-fake.ts +20 -5
  101. package/src/live-target.mjs +112 -37
  102. package/src/live.ts +116 -225
  103. package/src/media.ts +8 -1
  104. package/src/migrations.mjs +14 -6
  105. package/src/mux.ts +161 -64
  106. package/src/og/DefaultCard.tsx +40 -9
  107. package/src/og/fonts.mjs +16 -8
  108. package/src/og/images.mjs +43 -22
  109. package/src/og/index.mjs +119 -43
  110. package/src/og/native.mjs +11 -4
  111. package/src/range.mjs +15 -3
  112. package/src/router.ts +56 -6
  113. package/src/routes.mjs +73 -22
  114. package/src/run/admission.ts +80 -0
  115. package/src/run/boot.tsx +115 -0
  116. package/src/run/cache-presentation.ts +67 -0
  117. package/src/run/cache.ts +254 -0
  118. package/src/run/helpers.ts +197 -0
  119. package/src/run/listeners.ts +155 -0
  120. package/src/run/navigation.ts +382 -0
  121. package/src/run/page-skeleton.tsx +35 -0
  122. package/src/run/prefetch.ts +128 -0
  123. package/src/run/retain-view.ts +18 -0
  124. package/src/run/shape-preparation.ts +97 -0
  125. package/src/run/shape-styles.ts +72 -0
  126. package/src/run/source-context/pin.ts +124 -0
  127. package/src/run/source-context.ts +311 -0
  128. package/src/run.tsx +50 -178
  129. package/src/rust/common.ts +163 -0
  130. package/src/rust/native.ts +282 -0
  131. package/src/rust.ts +103 -229
  132. package/src/server/card.tsx +65 -0
  133. package/src/server/compression.ts +115 -0
  134. package/src/server/core.ts +70 -0
  135. package/src/server/dispatch.ts +70 -0
  136. package/src/server/form.ts +85 -0
  137. package/src/server/functions.ts +129 -0
  138. package/src/server/handler.ts +39 -0
  139. package/src/server/head.ts +96 -0
  140. package/src/server/media.ts +60 -0
  141. package/src/server/navigation-cache.ts +80 -0
  142. package/src/server/navigation.tsx +85 -0
  143. package/src/server/page-cold.ts +95 -0
  144. package/src/server/page-stream.tsx +181 -0
  145. package/src/server/page-types.ts +51 -0
  146. package/src/server/page-warm.ts +130 -0
  147. package/src/server/page.ts +69 -0
  148. package/src/server/payload.ts +89 -0
  149. package/src/server/prerender.ts +60 -0
  150. package/src/server/preview.tsx +67 -0
  151. package/src/server/render.ts +167 -0
  152. package/src/server/request.ts +98 -0
  153. package/src/server/resolver.ts +228 -0
  154. package/src/server/routes.ts +150 -0
  155. package/src/server/services.ts +69 -0
  156. package/src/server/text.ts +29 -0
  157. package/src/server/types.ts +160 -0
  158. package/src/server.tsx +82 -971
  159. package/src/shape.mjs +30 -6
  160. package/src/share/display.ts +50 -0
  161. package/src/share/network.ts +119 -0
  162. package/src/share/receive.ts +70 -0
  163. package/src/share/state.ts +30 -0
  164. package/src/share/tab-queue.ts +28 -0
  165. package/src/share-server.ts +128 -43
  166. package/src/share.ts +103 -209
  167. package/src/sheets.mjs +30 -18
  168. package/src/sidecar/build.mjs +170 -0
  169. package/src/sidecar/cargo.mjs +164 -0
  170. package/src/sidecar/common.mjs +17 -0
  171. package/src/sidecar/dev-build.mjs +91 -0
  172. package/src/sidecar/dev-watch.mjs +88 -0
  173. package/src/sidecar/manifest.mjs +19 -0
  174. package/src/sidecar/process.mjs +58 -0
  175. package/src/sidecar/refresh.mjs +19 -0
  176. package/src/sidecar.mjs +75 -416
  177. package/src/socket/check.ts +50 -0
  178. package/src/socket/deliver.ts +16 -0
  179. package/src/socket/publish.ts +56 -0
  180. package/src/socket/rooms.ts +41 -0
  181. package/src/socket/route.ts +79 -0
  182. package/src/socket/shared.ts +26 -0
  183. package/src/socket/types.ts +48 -0
  184. package/src/socket.ts +205 -194
  185. package/src/stream.ts +10 -1
  186. package/src/verify/app.ts +39 -0
  187. package/src/verify/cleanup.ts +27 -0
  188. package/src/verify/database.ts +11 -0
  189. package/src/verify/live.ts +63 -0
  190. package/src/verify/notify.ts +35 -0
  191. package/src/verify/skip.ts +19 -0
  192. package/src/verify/support.ts +149 -0
  193. package/src/verify/timing.ts +15 -0
  194. package/src/verify/trigger.ts +14 -0
  195. package/src/verify.ts +77 -176
  196. package/src/view.tsx +96 -11
  197. package/src/vite/asset-bytes.mjs +95 -0
  198. package/src/vite/assets.mjs +117 -0
  199. package/src/vite/config-hooks.mjs +166 -0
  200. package/src/vite/dev-hooks.mjs +103 -0
  201. package/src/vite/dev.mjs +102 -0
  202. package/src/vite/entries.mjs +145 -0
  203. package/src/vite/module-hooks.mjs +288 -0
  204. package/src/vite/route-modules.mjs +289 -0
  205. package/src/vite/server-functions.mjs +222 -0
  206. package/src/vite/shape-entry-css.mjs +187 -0
  207. package/src/vite/shape-override.mjs +226 -0
  208. package/src/vite/shared.mjs +51 -0
  209. package/src/vite/sheets.mjs +92 -0
  210. package/src/vite/state.mjs +89 -0
  211. package/src/vite/syntax.mjs +139 -0
  212. package/src/vite.mjs +152 -853
  213. package/src/capture.d.mts +0 -1
@@ -0,0 +1,396 @@
1
+ import { DEV, batch, createRoot, state, untrack, type State } from '@weft-js/core'
2
+ import { HANDOVER, SOCKETS, openLine, socketDown } from '../link'
3
+ import { FnError, keyOf, net, rt, track, type Fn } from '../data'
4
+ import type { ChannelStatus } from '../channel'
5
+
6
+ // ---- joins ----------------------------------------------------------------------------------------------------------
7
+
8
+ export interface ListSpec {
9
+ name: string
10
+ max: number
11
+ key?: ((m: never) => unknown) | undefined
12
+ }
13
+ export interface List extends ListSpec {
14
+ box: State<unknown[]>
15
+ }
16
+ export interface Handle {
17
+ on: Map<string, Set<(m: never) => void>>
18
+ resets: Set<() => void>
19
+ value?(v: { v: unknown; at: number }): void
20
+ }
21
+ export interface Join {
22
+ n: number
23
+ key: string
24
+ fn: Fn
25
+ params: unknown
26
+ refs: number
27
+ timer?: ReturnType<typeof setTimeout> | undefined
28
+ cursor?: string | undefined
29
+ joined: boolean
30
+ refused: boolean
31
+ status: State<ChannelStatus>
32
+ error: State<unknown>
33
+ resets: State<number>
34
+ lists: Map<string, List>
35
+ handles: Set<Handle>
36
+ /** `send`s waiting for the join. */
37
+ waiting: Set<() => void>
38
+ }
39
+
40
+ export const joins = new Map<string, Join>()
41
+ export const byN = new Map<number, Join>()
42
+ let nth = 0
43
+
44
+ const KEEP = 30_000
45
+ const ms = (keep: string | number = KEEP): number => {
46
+ if (typeof keep === 'number') return keep
47
+ const m = /^(\d+)(ms|s|m)$/.exec(keep)
48
+ return m ? +m[1]! * { ms: 1, s: 1000, m: 60_000 }[m[2] as 'ms' | 's' | 'm'] : Infinity
49
+ }
50
+
51
+ export function acquire(fn: Fn, params: unknown): Join {
52
+ const key = keyOf(fn.$id, params)
53
+ let j = joins.get(key)
54
+ if (!j) {
55
+ j = createRoot(
56
+ () => ({
57
+ n: ++nth,
58
+ key,
59
+ fn,
60
+ params,
61
+ refs: 0,
62
+ joined: false,
63
+ refused: false,
64
+ status: state<ChannelStatus>('connecting'),
65
+ error: state<unknown>(undefined),
66
+ resets: state(0),
67
+ lists: new Map(),
68
+ handles: new Set(),
69
+ waiting: new Set(),
70
+ }),
71
+ null,
72
+ )
73
+ if (DEV && net() && !net()!.resets.includes(reset)) net()!.resets.push(reset)
74
+ joins.set(key, j)
75
+ byN.set(j.n, j)
76
+ if (live) enter(j)
77
+ else start()
78
+ }
79
+ clearTimeout(j.timer)
80
+ j.timer = undefined
81
+ j.refs++
82
+ return j
83
+ }
84
+
85
+ function release(j: Join, keep?: string | number) {
86
+ if (--j.refs) return
87
+ const t = ms(keep)
88
+ if (t === Infinity) return
89
+ if (t) j.timer = setTimeout(() => drop(j), t)
90
+ else drop(j)
91
+ }
92
+
93
+ function drop(j: Join) {
94
+ joins.delete(j.key)
95
+ byN.delete(j.n)
96
+ j.status.value = 'closed'
97
+ if (live) send(['l', j.n])
98
+ for (const w of Array.from(j.waiting)) w()
99
+ if (!joins.size) live?.close()
100
+ }
101
+
102
+ export function hold(j: Join, keep?: string | number): () => void {
103
+ const token = {}
104
+ const stop = () => {
105
+ net()?.open.delete(token)
106
+ release(j, keep)
107
+ }
108
+ if (DEV) net()?.open.set(token, [`channel \`${j.key}\``, stop])
109
+ return stop
110
+ }
111
+
112
+ export function listOf(j: Join, spec: ListSpec): List {
113
+ const id = `${spec.name}:${spec.max}:${spec.key ? String(spec.key) : ''}`
114
+ let l = j.lists.get(id)
115
+ if (!l) j.lists.set(id, (l = createRoot(() => ({ ...spec, box: state<unknown[]>([]) }), null)))
116
+ return l
117
+ }
118
+
119
+ // ---- sends ----------------------------------------------------------------------------------------------------------
120
+
121
+ const ACK_WAIT = 10_000
122
+ const acks = new Map<number, { ok: () => void; no: (e: unknown) => void; n: number }>()
123
+ let ackId = 0
124
+
125
+ export async function post(j: Join, name: string, message: unknown): Promise<void> {
126
+ if (!SOCKETS && !(DEV && net()))
127
+ throw new FnError({ code: 'R024', message: "this deploy can't hold a socket: not sent" }, 503)
128
+ if (!j.joined && !j.refused) {
129
+ await new Promise<void>((ok) => {
130
+ const done = () => {
131
+ clearTimeout(t)
132
+ j.waiting.delete(done)
133
+ ok()
134
+ }
135
+ const t = setTimeout(done, ACK_WAIT)
136
+ j.waiting.add(done)
137
+ })
138
+ }
139
+ if (!j.joined)
140
+ throw new FnError(
141
+ {
142
+ ...(j.refused && { code: 'R011' }),
143
+ message: j.refused ? 'not allowed in this channel' : 'no connection: not sent',
144
+ },
145
+ j.refused ? 403 : 503,
146
+ )
147
+ const ack = ++ackId
148
+ const p = new Promise<void>((ok, no) => {
149
+ acks.set(ack, { ok, no, n: j.n })
150
+ })
151
+ send(['m', j.n, ack, name, message])
152
+ return track(p)
153
+ }
154
+
155
+ // ---- the socket ------------------------------------------------------------------------------------------------------
156
+
157
+ /** What the server sends a channel's client: the first element says which. */
158
+ export type Frame =
159
+ | ['h', { b?: string }]
160
+ | ['j', number, string | undefined]
161
+ | ['m', number, string | undefined, string, never]
162
+ | ['r', number, string | undefined]
163
+ | ['e', number, { code?: string; message?: string }]
164
+ | ['a', number, number, { code?: string; message?: string } | undefined]
165
+
166
+ /** One end of a socket, as a channel holds it. */
167
+ export interface SocketLink {
168
+ send(text: string): void
169
+ close(): void
170
+ }
171
+
172
+ /** This tab's line on the browser's one socket (link.ts). `done` gets the close code. */
173
+ function browser(onFrame: (d: Frame) => void, closed: (code: number) => void): SocketLink {
174
+ return openLine('c', '', (text) => onFrame(JSON.parse(text)), closed)
175
+ }
176
+
177
+ let fake: (onFrame: (d: Frame) => void, closed: (code: number) => void) => SocketLink
178
+ /** Keep the test transport resolved from the original entry. */
179
+ export function setFake(value: typeof fake) {
180
+ fake = value
181
+ }
182
+
183
+ const BASE = 500,
184
+ CAP = 30_000,
185
+ PING = 20_000,
186
+ DEAD = 45_000,
187
+ HIDDEN = 300_000
188
+
189
+ let live: SocketLink | undefined
190
+ let open = false
191
+ let running = false
192
+ let attempt = 0
193
+ let build: string | undefined
194
+ let nudge: (() => void) | undefined
195
+ let parked = false
196
+ let hiddenAt: ReturnType<typeof setTimeout> | undefined
197
+
198
+ /** A leader tab can be hidden while a sibling is looking: `@weft-js/start/share` says when this socket must stay. */
199
+ export const keepOpen: { stay?: (() => boolean) | undefined } = {}
200
+
201
+ const send = (f: unknown[]) => live?.send(JSON.stringify(f))
202
+ const statusAll = (s: ChannelStatus) => {
203
+ for (const j of joins.values()) if (!j.refused) j.status.value = s
204
+ }
205
+
206
+ function enter(j: Join) {
207
+ if (!open || !live) return
208
+ send(['j', j.n, j.fn.$id, j.params, j.cursor])
209
+ }
210
+
211
+ export function rejoin(j: Join) {
212
+ j.refused = false
213
+ j.status.value = 'connecting'
214
+ if (running) enter(j)
215
+ else start()
216
+ }
217
+
218
+ function start() {
219
+ if (running) return
220
+ // a function host: no socket to dial, so say so once instead of retrying forever
221
+ if (!SOCKETS && !(DEV && net())) return statusAll('unavailable')
222
+ running = true
223
+ queueMicrotask(() => void run())
224
+ }
225
+
226
+ function dispatch(j: Join, name: string, msg: never) {
227
+ if (name === '$v') {
228
+ for (const h of j.handles) h.value?.(msg)
229
+ return
230
+ }
231
+ for (const l of j.lists.values()) {
232
+ if (l.name !== name) continue
233
+ const was = untrack(() => l.box.value)
234
+ const key = l.key?.(msg)
235
+ const at = l.key ? was.findIndex((m) => l.key!(m as never) === key) : -1
236
+ const next = at >= 0 ? was.map((m, i) => (i === at ? msg : m)) : [...was, msg]
237
+ l.box.value = next.length > l.max ? next.slice(-l.max) : next
238
+ }
239
+ for (const h of j.handles) for (const f of h.on.get(name) ?? []) f(msg)
240
+ }
241
+
242
+ function frame(d: Frame) {
243
+ batch(() => {
244
+ const j = typeof d[1] === 'number' ? byN.get(d[1]) : undefined
245
+ if (d[0] === 'h') {
246
+ if (build && d[1].b && d[1].b !== build) rt.stale.value = true
247
+ build = d[1].b
248
+ open = true
249
+ attempt = 0
250
+ for (const x of joins.values()) if (!x.refused) enter(x)
251
+ } else if (d[0] === 'j' && j) {
252
+ j.cursor ??= d[2]
253
+ j.joined = true
254
+ j.error.value = undefined
255
+ j.status.value = 'open'
256
+ for (const w of Array.from(j.waiting)) w()
257
+ } else if (d[0] === 'm' && j) {
258
+ j.cursor = d[2]
259
+ dispatch(j, d[3], d[4])
260
+ } else if (d[0] === 'r' && j) {
261
+ j.cursor = d[2]
262
+ for (const l of j.lists.values()) l.box.value = []
263
+ j.resets.value++
264
+ for (const h of j.handles) for (const f of h.resets) f()
265
+ } else if (d[0] === 'e' && j) {
266
+ j.joined = false
267
+ j.refused = true
268
+ j.error.value = new FnError(d[2], d[2].code === 'R011' ? 403 : 400)
269
+ j.status.value = d[2].code === 'R011' ? 'unauthorized' : 'closed'
270
+ for (const w of Array.from(j.waiting)) w()
271
+ } else if (d[0] === 'a') {
272
+ const p = acks.get(d[2])
273
+ acks.delete(d[2])
274
+ if (d[3]) p?.no(new FnError(d[3], d[3].code === 'R011' ? 403 : 400))
275
+ else p?.ok()
276
+ }
277
+ })
278
+ }
279
+
280
+ const sleep = (t: number) =>
281
+ new Promise<void>((ok) => {
282
+ const timer = setTimeout(ok, t)
283
+ nudge = () => {
284
+ clearTimeout(timer)
285
+ nudge = undefined
286
+ ok()
287
+ }
288
+ })
289
+
290
+ async function run() {
291
+ listen(true)
292
+ const fakeNet = () => (DEV ? net() : undefined)
293
+ while (joins.size) {
294
+ if (parked || !navigator.onLine) {
295
+ statusAll('reconnecting')
296
+ await sleep(CAP)
297
+ continue
298
+ }
299
+ let code = 0
300
+ let dead: ReturnType<typeof setTimeout> | undefined
301
+ let ping: ReturnType<typeof setInterval> | undefined
302
+ const alive = () => {
303
+ clearTimeout(dead)
304
+ if (!fakeNet()) dead = setTimeout(() => live?.close(), DEAD)
305
+ }
306
+ await new Promise<void>((done) => {
307
+ const take = (d: Frame) => {
308
+ alive()
309
+ frame(d)
310
+ }
311
+ live = (fakeNet() ? fake : browser)(take, (c) => {
312
+ code = c
313
+ done()
314
+ })
315
+ alive()
316
+ if (!fakeNet()) ping = setInterval(() => send(['p']), PING)
317
+ })
318
+ clearTimeout(dead)
319
+ clearInterval(ping)
320
+ live = undefined
321
+ open = false
322
+ for (const j of joins.values()) j.joined = false
323
+ for (const [id, p] of acks) {
324
+ acks.delete(id)
325
+ p.no(
326
+ new FnError({ message: 'the connection dropped: this may or may not have been sent' }, 503),
327
+ )
328
+ }
329
+ if (!joins.size) break
330
+ const n = fakeNet()
331
+ if (n) n.reconnects++
332
+ if (code === 4401) for (const j of joins.values()) j.status.value = 'unauthorized'
333
+ else statusAll('reconnecting')
334
+ if (!n) await sleep(code === HANDOVER ? 0 : backoff())
335
+ }
336
+ running = false
337
+ attempt = 0
338
+ listen(false)
339
+ }
340
+
341
+ /** Full jitter, 0.5 s doubling to 30 s (research 30 §6). */
342
+ const backoff = () => Math.random() * Math.min(CAP, BASE * 2 ** attempt++)
343
+
344
+ // ---- the page -------------------------------------------------------------------------------------------------------
345
+
346
+ const wake = () => {
347
+ parked = false
348
+ nudge?.()
349
+ }
350
+ function park() {
351
+ parked = true
352
+ live?.close()
353
+ }
354
+ const parkHidden = () => {
355
+ if (keepOpen.stay?.()) hiddenAt = setTimeout(parkHidden, HIDDEN)
356
+ else park()
357
+ }
358
+ function onVisibility() {
359
+ clearTimeout(hiddenAt)
360
+ if (document.visibilityState === 'hidden') hiddenAt = setTimeout(parkHidden, HIDDEN)
361
+ else wake()
362
+ }
363
+ /** A sibling came back while this tab's socket was parked. */
364
+ export const wakeSocket = () => wake()
365
+ const onOffline = () => {
366
+ socketDown()
367
+ live?.close()
368
+ }
369
+
370
+ function listen(on: boolean) {
371
+ if (typeof window === 'undefined') return
372
+ const m = on ? 'addEventListener' : 'removeEventListener'
373
+ window[m]('online', wake)
374
+ window[m]('offline', onOffline)
375
+ window[m]('pagehide', park)
376
+ window[m]('pageshow', wake)
377
+ document[m]('visibilitychange', onVisibility)
378
+ if (!on) clearTimeout(hiddenAt)
379
+ }
380
+
381
+ /** @weft-js/core/test, after each test. */
382
+ function reset() {
383
+ for (const j of joins.values()) clearTimeout(j.timer)
384
+ joins.clear()
385
+ byN.clear()
386
+ acks.clear()
387
+ live?.close()
388
+ nudge?.()
389
+ build = undefined
390
+ parked = false
391
+ attempt = 0
392
+ net()!.reconnects = 0
393
+ untrack(() => {
394
+ rt.stale.value = false
395
+ })
396
+ }
@@ -6,9 +6,17 @@ import type { Frame, SocketLink } from './channel'
6
6
  /** A socket to the real server code in this process. */
7
7
  const exec_ = async () => (runtime.exec ??= await import('./exec'))
8
8
 
9
- export async function open(frame: (d: Frame) => void, done: (code: number) => void, up: () => void): Promise<SocketLink> {
9
+ export async function open(
10
+ frame: (d: Frame) => void,
11
+ done: (code: number) => void,
12
+ up: () => void,
13
+ ): Promise<SocketLink> {
10
14
  const n = net()!
11
- const [{ hub, resetHub }, { attach, publish }, exec] = await Promise.all([import('./hub'), import('./socket'), exec_()])
15
+ const [{ hub, resetHub }, { attach, publish }, exec] = await Promise.all([
16
+ import('./hub'),
17
+ import('./socket'),
18
+ exec_(),
19
+ ])
12
20
  if (!n.online()) throw new Error('offline')
13
21
  if (!n.resets.includes(resetHub)) n.resets.push(resetHub)
14
22
  const h = hub()
@@ -36,7 +44,10 @@ export async function open(frame: (d: Frame) => void, done: (code: number) => vo
36
44
  const socket = await attach(peer, request, fakeScope(exec, n, request), h)
37
45
  n.ws = {
38
46
  publish: (id, params, name, msg) => publish(fns.get(id)!, params, name, msg),
39
- drop: () => { peer.close(1006); socket.close() }, // both ends, as a real cut: the server runs `left`
47
+ drop: () => {
48
+ peer.close(1006)
49
+ socket.close()
50
+ }, // both ends, as a real cut: the server runs `left`
40
51
  recheck: () => socket.recheck(),
41
52
  }
42
53
  up()
@@ -46,7 +57,11 @@ export async function open(frame: (d: Frame) => void, done: (code: number) => vo
46
57
  if (f[0] === 'm') n.sent.push({ id: rooms.get(f[1]) ?? '', name: f[3], msg: f[4] })
47
58
  if (f[0] === 'j') {
48
59
  rooms.set(f[1], f[2])
49
- void track(new Promise<void>(ok => { joining.set(f[1], ok) })) // `net.settle()` waits for the join's answer
60
+ void track(
61
+ new Promise<void>((ok) => {
62
+ joining.set(f[1], ok)
63
+ }),
64
+ ) // `net.settle()` waits for the join's answer
50
65
  }
51
66
  socket.message(text)
52
67
  },