@weft-js/start 0.1.0 → 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 +51 -10
  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 +124 -490
  32. package/src/client.tsx +9 -2
  33. package/src/contract/describe.mjs +231 -0
  34. package/src/contract.mjs +146 -155
  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 +97 -168
  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 +113 -35
  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 -174
  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 -964
  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,56 @@
1
+ import type { PublishOptions } from '../channel'
2
+ import { stable, StartError, type Fn } from '../data'
3
+ import { hub } from '../hub'
4
+ import { check } from './check'
5
+
6
+ const roomName = (id: string, params: unknown) => `weft:c:${id}:${stable(params)}`
7
+
8
+ /** `room.publish` and `room.in(params).publish`. */
9
+ export async function publish(
10
+ fn: Fn,
11
+ params: unknown,
12
+ name: string,
13
+ message: unknown,
14
+ o: PublishOptions & { from?: string } = {},
15
+ ) {
16
+ const h = hub()
17
+ const valid = await check(
18
+ fn.kind === 'shared' ? fn.def.key : fn.def.params,
19
+ params,
20
+ `\`${fn.$id}\`'s params`,
21
+ )
22
+ if (h.dev) {
23
+ const schema = Object.hasOwn(fn.def.server ?? {}, name) ? fn.def.server![name] : undefined
24
+ if (!schema)
25
+ throw new StartError(
26
+ 'C002',
27
+ `\`${fn.$id}\` has no server message \`${name}\``,
28
+ 'add it to `server`',
29
+ { status: 500 },
30
+ )
31
+ const r = await schema['~standard'].validate(message)
32
+ if (r.issues)
33
+ throw new StartError(
34
+ 'C002',
35
+ `\`${fn.$id}.${name}\` was published with a message its schema rejects: ${r.issues.map((i) => i.message).join('; ')}`,
36
+ 'fix the message, or the schema',
37
+ { status: 500 },
38
+ )
39
+ }
40
+ // `v` first: the hub reads it off the front of the string, to leave the ring alone.
41
+ const raw = JSON.stringify({
42
+ ...(o.volatile && { v: 1 }),
43
+ ...(o.from && { f: o.from }),
44
+ ...(o.exceptSelf && { x: 1 }),
45
+ ...(o.to && { t: o.to }),
46
+ n: name,
47
+ m: message,
48
+ })
49
+ if (raw.length > h.limits.message)
50
+ throw new StartError(
51
+ 'R008',
52
+ `a \`${fn.$id}\` message is ${raw.length} bytes; a message may be ${h.limits.message}`,
53
+ 'publish less, or an id subscribers fetch',
54
+ )
55
+ await h.bus.publish(roomName(fn.$id, valid), raw)
56
+ }
@@ -0,0 +1,41 @@
1
+ import type { Hub, Ev } from '../hub'
2
+ import type { Hubbed, Room, Value } from './types'
3
+
4
+ export const VALUE = '$v',
5
+ ASK = '$?'
6
+ const VALUES = 10_000
7
+
8
+ export function room(h: Hub, st: Hubbed, name: string, shared: boolean): Room {
9
+ let r = st.rooms.get(name)
10
+ if (r) return (r.refs++, r)
11
+ const ch = h.channel(name)
12
+ const on = (ev: Ev | null) => {
13
+ if (!ev || !shared) return
14
+ const m = (ev.p ??= JSON.parse(ev.raw)) as { n: string; m: never }
15
+ if (m.n === VALUE) store(st, name, m.m)
16
+ else if (m.n === ASK) {
17
+ const v = st.values.get(name)
18
+ if (v) void h.bus.publish(name, JSON.stringify({ n: VALUE, m: v }))
19
+ }
20
+ }
21
+ ch.listeners.add(on)
22
+ r = { name, ch, refs: 1, shared, on }
23
+ st.rooms.set(name, r)
24
+ if (shared && !st.values.has(name))
25
+ void ch.ready.then(() => h.bus.publish(name, JSON.stringify({ n: ASK })))
26
+ return r
27
+ }
28
+
29
+ export function leaveRoom(h: Hub, st: Hubbed, r: Room) {
30
+ if (--r.refs) return
31
+ st.rooms.delete(r.name)
32
+ h.unlisten(r.ch, r.on)
33
+ }
34
+
35
+ export function store(st: Hubbed, name: string, v: Value) {
36
+ const had = st.values.get(name)
37
+ if (had && (had.at > v.at || (had.at === v.at && had.o >= v.o))) return
38
+ st.values.delete(name) // last in the map: the oldest key goes first
39
+ st.values.set(name, v)
40
+ if (st.values.size > VALUES) st.values.delete(st.values.keys().next().value!)
41
+ }
@@ -0,0 +1,79 @@
1
+ import { checkOrigins } from '../hub'
2
+ import type { Scope } from '../exec'
3
+ import type { Peer, Attached } from '../socket'
4
+
5
+ interface WsPeer {
6
+ id: string
7
+ send(text: string): unknown
8
+ close(code?: number, reason?: string): unknown
9
+ websocket?: { bufferedAmount?: number }
10
+ }
11
+
12
+ /** Nitro reads `crossws` off the response (research 32 §1). A refusal is a hook that throws, so no socket opens. */
13
+ export function socketRouteWith(
14
+ request: Request,
15
+ scope: Scope,
16
+ origins: string[],
17
+ attach: (peer: Peer, request: Request, scope: Scope) => Promise<Attached>,
18
+ ): Promise<Response> {
19
+ const hooks = (crossws: object) =>
20
+ Promise.resolve(Object.assign(new Response('Upgrade required', { status: 426 }), { crossws }))
21
+ const turnAway = (code: string, message: string, status: number) => {
22
+ scope.options.onDiagnostic?.({ code, severity: 'error', message })
23
+ // oxlint-disable only-throw-error -- crossws answers an upgrade with the Response its hook throws
24
+ return hooks({
25
+ upgrade() {
26
+ throw new Response(message, { status }) /* oxlint-enable only-throw-error */
27
+ },
28
+ })
29
+ }
30
+ if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket')
31
+ return Promise.resolve(
32
+ new Response('Upgrade required', { status: 426, headers: { 'cache-control': 'no-store' } }),
33
+ )
34
+ try {
35
+ checkOrigins(origins)
36
+ } catch (e) {
37
+ return turnAway('R004', (e as Error).message, 500)
38
+ }
39
+ const origin = request.headers.get('origin')
40
+ if (!origin || (origin !== new URL(request.url).origin && !origins.includes(origin)))
41
+ return turnAway(
42
+ 'R010',
43
+ `R010: origin \`${origin ?? 'none'}\` may not open a socket: list it in \`realtime({ origins })\``,
44
+ 403,
45
+ )
46
+ const peers = new Map<string, Promise<Pick<Attached, 'message' | 'close'>>>()
47
+ const mux = new URL(request.url).searchParams.has('mux')
48
+ return hooks({
49
+ open(peer: WsPeer) {
50
+ const p: Peer = {
51
+ send: (t) => void peer.send(t),
52
+ close: (code, reason) => peer.close(code, reason),
53
+ backlog: () => peer.websocket?.bufferedAmount ?? 0,
54
+ }
55
+ peers.set(
56
+ peer.id,
57
+ (mux
58
+ ? import('../mux').then((m) => m.muxed(p, request, scope))
59
+ : attach(p, request, scope)
60
+ ).catch((e) => {
61
+ scope.options.onDiagnostic?.({
62
+ code: 'E_RT',
63
+ severity: 'error',
64
+ message: `/_weft/ws: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`,
65
+ })
66
+ peer.close(1011, 'context failed')
67
+ return { message() {}, close() {} }
68
+ }),
69
+ )
70
+ },
71
+ message(peer: WsPeer, m: { text(): string }) {
72
+ void peers.get(peer.id)?.then((a) => a.message(m.text()))
73
+ },
74
+ close(peer: WsPeer) {
75
+ void peers.get(peer.id)?.then((a) => a.close())
76
+ peers.delete(peer.id)
77
+ },
78
+ })
79
+ }
@@ -0,0 +1,26 @@
1
+ import type { Fn } from '../data'
2
+ import { stable } from '../data'
3
+ import { hub, type Hub } from '../hub'
4
+ import { check } from './check'
5
+ import { store, VALUE } from './rooms'
6
+ import type { Hubbed, Value } from './types'
7
+
8
+ const roomName = (id: string, params: unknown) => `weft:c:${id}:${stable(params)}`
9
+
10
+ export function sharedWriter(stateOf: (h: Hub) => Hubbed) {
11
+ /** A shared value written by the server: `deckSlide.in(key).set(3)`. */
12
+ return async function setShared(fn: Fn, key: unknown, value: unknown) {
13
+ const h = hub()
14
+ const valid = await check(fn.def.key, key, `\`${fn.$id}\`'s key`)
15
+ const v = await check(fn.def.value, value, `\`${fn.$id}\`'s value`)
16
+ const st = stateOf(h),
17
+ name = roomName(fn.$id, valid)
18
+ const stamp: Value = {
19
+ v,
20
+ at: Math.max(Date.now(), (st.values.get(name)?.at ?? 0) + 1),
21
+ o: h.epoch,
22
+ }
23
+ store(st, name, stamp)
24
+ await h.bus.publish(name, JSON.stringify({ n: VALUE, m: stamp }))
25
+ }
26
+ }
@@ -0,0 +1,48 @@
1
+ import type { Context, Fn } from '../data'
2
+ import type { ScopeOptions } from '../exec'
3
+ import type { Channel, Ev } from '../hub'
4
+ import type { Peer } from '../socket'
5
+
6
+ export interface Join {
7
+ n: number
8
+ fn: Fn
9
+ params: unknown
10
+ room?: Room
11
+ on?: (ev: Ev | null) => void
12
+ bucket: Bucket
13
+ gone?: boolean
14
+ chain: Promise<unknown>
15
+ }
16
+ export interface Bucket {
17
+ tokens: number
18
+ at: number
19
+ }
20
+ export interface Sock {
21
+ k: string
22
+ peer: Peer
23
+ request: Request
24
+ options: ScopeOptions
25
+ ctx: Context
26
+ user?: string | undefined
27
+ joins: Map<number, Join>
28
+ bucket: Bucket
29
+ last: number
30
+ timers: ReturnType<typeof setTimeout>[]
31
+ }
32
+ export interface Room {
33
+ name: string
34
+ ch: Channel
35
+ refs: number
36
+ shared: boolean
37
+ on: (ev: Ev | null) => void
38
+ }
39
+ export interface Value {
40
+ v: unknown
41
+ at: number
42
+ o: string
43
+ }
44
+ export interface Hubbed {
45
+ socks: Set<Sock>
46
+ rooms: Map<string, Room>
47
+ values: Map<string, Value>
48
+ }