@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.
- package/README.md +36 -3
- package/crate/Cargo.lock +1347 -0
- package/crate/Cargo.toml +72 -0
- package/crate/rust-toolchain.toml +3 -0
- package/crate/src/build/emit.rs +103 -0
- package/crate/src/build.rs +367 -0
- package/crate/src/ctx.rs +186 -0
- package/crate/src/envelope.rs +163 -0
- package/crate/src/functions.rs +173 -0
- package/crate/src/lib.rs +118 -0
- package/crate/src/native.rs +212 -0
- package/crate/src/otel.rs +62 -0
- package/crate/src/server.rs +203 -0
- package/package.json +46 -37
- package/src/app.mjs +38 -8
- package/src/asset-receipt.d.mts +13 -0
- package/src/asset-receipt.mjs +61 -0
- package/src/auth/better-auth.ts +215 -17
- package/src/auth/drizzle.ts +1 -1
- package/src/auth.ts +21 -6
- package/src/boot.ts +19 -8
- package/src/bus/postgres.ts +32 -19
- package/src/bus.ts +7 -4
- package/src/{capture.mjs → capture.ts} +34 -1
- package/src/changes-sql.d.mts +8 -2
- package/src/changes-sql.mjs +26 -10
- package/src/changes.ts +165 -66
- package/src/channel/connections.ts +216 -0
- package/src/channel/socket.ts +396 -0
- package/src/channel-fake.ts +19 -4
- package/src/channel.ts +120 -492
- package/src/client.tsx +9 -2
- package/src/contract/describe.mjs +231 -0
- package/src/contract.mjs +144 -157
- package/src/data/cache.ts +354 -0
- package/src/data/definitions.ts +94 -0
- package/src/data/draft.ts +104 -0
- package/src/data/foundation.ts +95 -0
- package/src/data/live-api.ts +22 -0
- package/src/data/mutation.ts +138 -0
- package/src/data/primitives.ts +135 -0
- package/src/data/query.ts +106 -0
- package/src/data/transport.ts +121 -0
- package/src/data/types.ts +303 -0
- package/src/data.ts +69 -894
- package/src/db/bun-sqlite.d.ts +4 -1
- package/src/db/changes-mysql.ts +61 -21
- package/src/db/changes-postgres.ts +197 -118
- package/src/db/changes-sqlite.ts +131 -64
- package/src/db/core.ts +38 -13
- package/src/db/index.ts +20 -4
- package/src/db/mysql.ts +22 -6
- package/src/db/postgres.ts +49 -13
- package/src/db/sqlite.ts +53 -27
- package/src/derive.d.mts +4 -1
- package/src/derive.mjs +136 -39
- package/src/dev-runtime.mjs +8 -4
- package/src/exec/errors.ts +57 -0
- package/src/exec/landing.ts +66 -0
- package/src/exec/scope.ts +174 -0
- package/src/exec/touch.ts +80 -0
- package/src/exec/validation.ts +176 -0
- package/src/exec.ts +191 -341
- package/src/extension.ts +8 -2
- package/src/first-paint.ts +26 -9
- package/src/form/value.ts +15 -0
- package/src/form.tsx +132 -43
- package/src/hub/channels.ts +102 -0
- package/src/hub/connections.ts +147 -0
- package/src/hub/delivery.ts +62 -0
- package/src/hub/feed.ts +168 -0
- package/src/hub/presence.ts +64 -0
- package/src/hub/route.ts +158 -0
- package/src/hub/subscriptions.ts +212 -0
- package/src/hub/topic.ts +98 -0
- package/src/hub/tracked.ts +12 -0
- package/src/hub/types.ts +190 -0
- package/src/hub/view.ts +147 -0
- package/src/hub.ts +132 -758
- package/src/index.ts +84 -10
- package/src/inline.mjs +356 -0
- package/src/inspect/compare.mjs +162 -0
- package/src/inspect/routes.mjs +154 -0
- package/src/inspect/samples.mjs +59 -0
- package/src/inspect/url.mjs +2 -0
- package/src/inspect.mjs +146 -187
- package/src/link/election.ts +178 -0
- package/src/link/lines.ts +27 -0
- package/src/link.ts +90 -166
- package/src/list-drizzle.ts +104 -18
- package/src/list.ts +127 -36
- package/src/listen.ts +32 -6
- package/src/live/api.ts +18 -0
- package/src/live/backoff.ts +6 -0
- package/src/live/frames.ts +42 -0
- package/src/live/read.ts +77 -0
- package/src/live/sub-types.ts +24 -0
- package/src/live/transport.ts +86 -0
- package/src/live/types.ts +52 -0
- package/src/live-fake.ts +20 -5
- package/src/live-target.mjs +112 -37
- package/src/live.ts +116 -225
- package/src/media.ts +8 -1
- package/src/migrations.mjs +14 -6
- package/src/mux.ts +161 -64
- package/src/og/DefaultCard.tsx +40 -9
- package/src/og/fonts.mjs +16 -8
- package/src/og/images.mjs +43 -22
- package/src/og/index.mjs +119 -43
- package/src/og/native.mjs +11 -4
- package/src/range.mjs +15 -3
- package/src/router.ts +56 -6
- package/src/routes.mjs +73 -22
- package/src/run/admission.ts +80 -0
- package/src/run/boot.tsx +115 -0
- package/src/run/cache-presentation.ts +67 -0
- package/src/run/cache.ts +254 -0
- package/src/run/helpers.ts +197 -0
- package/src/run/listeners.ts +155 -0
- package/src/run/navigation.ts +382 -0
- package/src/run/page-skeleton.tsx +35 -0
- package/src/run/prefetch.ts +128 -0
- package/src/run/retain-view.ts +18 -0
- package/src/run/shape-preparation.ts +97 -0
- package/src/run/shape-styles.ts +72 -0
- package/src/run/source-context/pin.ts +124 -0
- package/src/run/source-context.ts +311 -0
- package/src/run.tsx +50 -178
- package/src/rust/common.ts +163 -0
- package/src/rust/native.ts +282 -0
- package/src/rust.ts +103 -229
- package/src/server/card.tsx +65 -0
- package/src/server/compression.ts +115 -0
- package/src/server/core.ts +70 -0
- package/src/server/dispatch.ts +70 -0
- package/src/server/form.ts +85 -0
- package/src/server/functions.ts +129 -0
- package/src/server/handler.ts +39 -0
- package/src/server/head.ts +96 -0
- package/src/server/media.ts +60 -0
- package/src/server/navigation-cache.ts +80 -0
- package/src/server/navigation.tsx +85 -0
- package/src/server/page-cold.ts +95 -0
- package/src/server/page-stream.tsx +181 -0
- package/src/server/page-types.ts +51 -0
- package/src/server/page-warm.ts +130 -0
- package/src/server/page.ts +69 -0
- package/src/server/payload.ts +89 -0
- package/src/server/prerender.ts +60 -0
- package/src/server/preview.tsx +67 -0
- package/src/server/render.ts +167 -0
- package/src/server/request.ts +98 -0
- package/src/server/resolver.ts +228 -0
- package/src/server/routes.ts +150 -0
- package/src/server/services.ts +69 -0
- package/src/server/text.ts +29 -0
- package/src/server/types.ts +160 -0
- package/src/server.tsx +82 -971
- package/src/shape.mjs +30 -6
- package/src/share/display.ts +50 -0
- package/src/share/network.ts +119 -0
- package/src/share/receive.ts +70 -0
- package/src/share/state.ts +30 -0
- package/src/share/tab-queue.ts +28 -0
- package/src/share-server.ts +128 -43
- package/src/share.ts +103 -209
- package/src/sheets.mjs +30 -18
- package/src/sidecar/build.mjs +170 -0
- package/src/sidecar/cargo.mjs +164 -0
- package/src/sidecar/common.mjs +17 -0
- package/src/sidecar/dev-build.mjs +91 -0
- package/src/sidecar/dev-watch.mjs +88 -0
- package/src/sidecar/manifest.mjs +19 -0
- package/src/sidecar/process.mjs +58 -0
- package/src/sidecar/refresh.mjs +19 -0
- package/src/sidecar.mjs +75 -416
- package/src/socket/check.ts +50 -0
- package/src/socket/deliver.ts +16 -0
- package/src/socket/publish.ts +56 -0
- package/src/socket/rooms.ts +41 -0
- package/src/socket/route.ts +79 -0
- package/src/socket/shared.ts +26 -0
- package/src/socket/types.ts +48 -0
- package/src/socket.ts +205 -194
- package/src/stream.ts +10 -1
- package/src/verify/app.ts +39 -0
- package/src/verify/cleanup.ts +27 -0
- package/src/verify/database.ts +11 -0
- package/src/verify/live.ts +63 -0
- package/src/verify/notify.ts +35 -0
- package/src/verify/skip.ts +19 -0
- package/src/verify/support.ts +149 -0
- package/src/verify/timing.ts +15 -0
- package/src/verify/trigger.ts +14 -0
- package/src/verify.ts +77 -176
- package/src/view.tsx +96 -11
- package/src/vite/asset-bytes.mjs +95 -0
- package/src/vite/assets.mjs +117 -0
- package/src/vite/config-hooks.mjs +166 -0
- package/src/vite/dev-hooks.mjs +103 -0
- package/src/vite/dev.mjs +102 -0
- package/src/vite/entries.mjs +145 -0
- package/src/vite/module-hooks.mjs +288 -0
- package/src/vite/route-modules.mjs +289 -0
- package/src/vite/server-functions.mjs +222 -0
- package/src/vite/shape-entry-css.mjs +187 -0
- package/src/vite/shape-override.mjs +226 -0
- package/src/vite/shared.mjs +51 -0
- package/src/vite/sheets.mjs +92 -0
- package/src/vite/state.mjs +89 -0
- package/src/vite/syntax.mjs +139 -0
- package/src/vite.mjs +152 -853
- 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
|
+
}
|