@brust/core 2.0.0-alpha.3 → 2.0.0-alpha.31
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 +66 -6
- package/native/index.d.ts +82 -4
- package/native/index.js +58 -54
- package/package.json +29 -15
- package/src/actions/client.ts +53 -7
- package/src/actions/elysia.ts +1 -1
- package/src/actions/load.ts +82 -1
- package/src/actions/prefix.ts +44 -2
- package/src/actions-bridge.ts +44 -9
- package/src/assets.d.ts +55 -0
- package/src/browser.ts +6 -1
- package/src/build/actions-only.ts +86 -0
- package/src/build/build-eval.ts +332 -0
- package/src/build/build-icons.ts +69 -0
- package/src/build/bundle.ts +466 -54
- package/src/build/cf-assets.ts +4 -2
- package/src/build/cf-entry.ts +9 -4
- package/src/build/cf-wrangler.ts +3 -1
- package/src/build/cloudflare.ts +34 -13
- package/src/build/compile.ts +263 -6
- package/src/build/config.ts +138 -11
- package/src/build/css.ts +353 -0
- package/src/build/esm-resolve.ts +170 -0
- package/src/build/features.ts +119 -0
- package/src/build/index.ts +210 -27
- package/src/build/manifest.ts +94 -7
- package/src/build/pkg-src.ts +514 -0
- package/src/build/recognised.ts +19 -0
- package/src/build/scan.ts +71 -20
- package/src/build/server-only.ts +62 -0
- package/src/build/ssg-render.ts +65 -0
- package/src/build/ssg.ts +146 -0
- package/src/build/static-worker.ts +91 -0
- package/src/build/static.ts +259 -0
- package/src/build/tailwind.ts +117 -0
- package/src/build/tsconfig-paths.ts +67 -0
- package/src/build-time.ts +10 -0
- package/src/cache-adapter/types.ts +1 -1
- package/src/cf.ts +4 -1
- package/src/cli.ts +40 -4
- package/src/config.ts +111 -25
- package/src/css-modules.d.ts +8 -0
- package/src/dev/bun.ts +378 -0
- package/src/dev/client.ts +92 -0
- package/src/dev/devtools-data.ts +23 -0
- package/src/dev/devtools.ts +358 -0
- package/src/dev/diag.ts +30 -0
- package/src/dev/proxy.ts +256 -0
- package/src/dev/tui.ts +144 -0
- package/src/dev/watch.ts +130 -0
- package/src/dev.ts +15 -28
- package/src/handlers.ts +116 -10
- package/src/index.ts +6 -1
- package/src/native.ts +32 -1
- package/src/navigate-browser.ts +19 -0
- package/src/navigate.ts +19 -0
- package/src/navigation.ts +91 -0
- package/src/routes.ts +31 -1
- package/src/run.ts +65 -13
- package/src/shadcn/init.ts +513 -0
- package/src/shadcn/jsonc.ts +129 -0
- package/src/shadcn/registry.ts +149 -0
- package/src/store/builtin.ts +4 -0
- package/src/worker.ts +105 -22
package/src/build/manifest.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { FlatRoute } from '../routes'
|
|
7
7
|
import type { Compiled, ComponentIR } from './compile'
|
|
8
8
|
import { BuildError } from './errors'
|
|
9
|
+
import { featuresOf } from './features'
|
|
9
10
|
|
|
10
11
|
export interface JobCache {
|
|
11
12
|
key: string | null
|
|
@@ -29,6 +30,12 @@ export interface ChildRecord {
|
|
|
29
30
|
id: string
|
|
30
31
|
instances: string
|
|
31
32
|
props: Record<string, string>
|
|
33
|
+
/** F105: constant props; part of the child's job keys. */
|
|
34
|
+
literals?: Record<string, unknown>
|
|
35
|
+
/** F105: jinja over `__props` (and `__row`/`__idx` per row) the server evaluates. */
|
|
36
|
+
exprs?: Record<string, string>
|
|
37
|
+
/** F142 R34.43: the list of a `per-row-literal` instance (a constant the compiler read). */
|
|
38
|
+
rows?: unknown[]
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface ComponentRecord {
|
|
@@ -40,6 +47,8 @@ export interface ComponentRecord {
|
|
|
40
47
|
needs_worker: boolean
|
|
41
48
|
use_id_slots: number
|
|
42
49
|
stores: string[]
|
|
50
|
+
/** L33: the client runtime feature modules (`assets.features` keys) this component's output needs, in load order. */
|
|
51
|
+
features: string[]
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
export interface RouteRecord {
|
|
@@ -49,6 +58,8 @@ export interface RouteRecord {
|
|
|
49
58
|
loaders: string[]
|
|
50
59
|
cache: { ttl_seconds: number; prefix: string | null; bypass: true | string | null; tags: string[] } | null
|
|
51
60
|
catch_all: boolean
|
|
61
|
+
/** L19 (R19.3): `client/<file>.css` in cascade order; absent when the route has none. */
|
|
62
|
+
css?: string[]
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
/** `manifest.actions` (A3): `entry` is relative to dist; `maxBodyBytes`/`maxResponseBytes` are camelCase by contract. */
|
|
@@ -58,17 +69,79 @@ export interface ActionsRecord {
|
|
|
58
69
|
maxBodyBytes: number
|
|
59
70
|
maxResponseBytes: number
|
|
60
71
|
crossSite: 'reject' | 'allow'
|
|
72
|
+
/** F475: `[actions] stream` and its bounds; all absent without a `stream` list. */
|
|
73
|
+
stream?: string[]
|
|
74
|
+
maxStreamBytes?: number
|
|
75
|
+
streamIdleMs?: number
|
|
76
|
+
streamTotalMs?: number
|
|
77
|
+
maxStreams?: number
|
|
61
78
|
}
|
|
62
79
|
|
|
80
|
+
/** The manifest format the Rust loader accepts (`MANIFEST_VERSION` in brust-core `manifest.rs`): the one place TS names it. */
|
|
81
|
+
export const MANIFEST_VERSION = 3 as const
|
|
82
|
+
|
|
63
83
|
export interface ManifestJson {
|
|
64
|
-
version:
|
|
84
|
+
version: typeof MANIFEST_VERSION
|
|
65
85
|
routes: RouteRecord[]
|
|
66
86
|
components: Record<string, ComponentRecord>
|
|
67
|
-
|
|
87
|
+
/** `runtime` is the core; `features` maps each feature module name to its file (L33). */
|
|
88
|
+
assets: {
|
|
89
|
+
runtime: string
|
|
90
|
+
features: Record<string, string>
|
|
91
|
+
/** F453: the SPA-navigation module `client/nav-<hex>.js`; the `nav` tag is emitted only when it is set. */
|
|
92
|
+
nav?: string
|
|
93
|
+
/** F453: the build id (`data-brust-build`): a digest of the rest of the manifest, set last by `brust build`. */
|
|
94
|
+
build?: string
|
|
95
|
+
}
|
|
68
96
|
jobs_module: string
|
|
69
97
|
actions: ActionsRecord | null
|
|
98
|
+
/** L16 R16.3: an actions-only build (no routes, no templates); everything but the prefix is a 404 problem+json. */
|
|
99
|
+
actions_only?: true
|
|
100
|
+
/** L16 R16.4b: a page-only build: no local actions app, `base_url` points at the deployment that has it. */
|
|
101
|
+
actions_remote?: { prefix: string }
|
|
70
102
|
/** `[cache]` (cache adapters); absent without one (the manifest is then byte-identical). */
|
|
71
103
|
cache?: CacheRecord
|
|
104
|
+
/** F165 (R40.1): `brust start` spawns its worker pool (`needsWorkers`). Read by `run.ts` only; an
|
|
105
|
+
* absent value (an older or actions-only manifest) means the pool is needed. */
|
|
106
|
+
needs_workers?: boolean
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** F165 (R40.1): whether any request can call a worker. Workers answer four kinds of call
|
|
110
|
+
* (worker.ts): `loader`, `jobs` (precompute and SSR), `fetch` (the local actions app) and `cache`
|
|
111
|
+
* (the adapter). Only the jobs a request can plan count (`reachedJobs`, F183); a store read counts too (`readsStores`). */
|
|
112
|
+
export function needsWorkers(m: Pick<ManifestJson, 'routes' | 'components' | 'actions' | 'cache'>): boolean {
|
|
113
|
+
const planned = [...reachedJobs(m).planned].some((id) => (m.components[id]?.jobs.length ?? 0) > 0)
|
|
114
|
+
return planned || m.routes.some((r) => r.loaders.length > 0) || readsStores(m) || m.actions !== null || m.cache !== undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A route whose chain reads a store (its components, their children and job targets, transitively) gets a loader call
|
|
118
|
+
* from brust-core even with no loader of its own: the worker builds the `__store` snapshot (pipeline.rs `reads_store`).
|
|
119
|
+
* Without a pool that call answers 503. */
|
|
120
|
+
function readsStores(m: Pick<ManifestJson, 'routes' | 'components'>): boolean {
|
|
121
|
+
const seen = new Set<string>()
|
|
122
|
+
const reads = (id: string): boolean => {
|
|
123
|
+
if (seen.has(id)) return false
|
|
124
|
+
seen.add(id)
|
|
125
|
+
const c = m.components[id]
|
|
126
|
+
return !!c && (c.stores.length > 0 || c.children.some((k) => reads(k.id)) || c.jobs.some((j) => j.target !== undefined && reads(j.target)))
|
|
127
|
+
}
|
|
128
|
+
return m.routes.some((r) => r.chain.some(reads))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** F183: the jobs a request can call, as brust-core plans them (`collect_jobs`, pipeline.rs): every job of each
|
|
132
|
+
* route chain component and of each of its direct `children[]` records (`planned`; nothing deeper has a job,
|
|
133
|
+
* `linkNativeChunks`), and the `ssr` of each component one of those jobs targets (`ssr`; the worker runs the
|
|
134
|
+
* target's, handlers.ts). `jobs.js` leaves out every other entry: no request reaches it. */
|
|
135
|
+
export function reachedJobs(m: Pick<ManifestJson, 'routes' | 'components'>): { planned: Set<string>; ssr: Set<string> } {
|
|
136
|
+
const planned = new Set<string>()
|
|
137
|
+
for (const r of m.routes)
|
|
138
|
+
for (const id of r.chain) {
|
|
139
|
+
planned.add(id)
|
|
140
|
+
for (const ch of m.components[id]?.children ?? []) planned.add(ch.id)
|
|
141
|
+
}
|
|
142
|
+
const ssr = new Set<string>()
|
|
143
|
+
for (const id of planned) for (const j of m.components[id]?.jobs ?? []) if (j.kind === 'ssr' && j.target !== undefined) ssr.add(j.target)
|
|
144
|
+
return { planned, ssr }
|
|
72
145
|
}
|
|
73
146
|
|
|
74
147
|
/** `manifest.cache` / `CONFIG.cache`: the adapter the runtime loads (`cache-adapter/load.ts`). */
|
|
@@ -226,15 +299,23 @@ export function childRecords(c: Compiled, clientOnly: string[] = []): ChildRecor
|
|
|
226
299
|
if (inst.loops.length > 1)
|
|
227
300
|
throw new BuildError('nested-instance', `${ir.id}: child ${inst.child_id} sits in nested lists (M3)`)
|
|
228
301
|
const list = inst.loops[0]
|
|
229
|
-
|
|
302
|
+
const literal = list === null && Array.isArray(inst.rows_literal)
|
|
303
|
+
if (list === null && !literal)
|
|
230
304
|
throw new BuildError('instance-list-path', `${ir.id}: child ${inst.child_id} sits in a list that is not a props path`)
|
|
231
305
|
const props: Record<string, string> = {}
|
|
232
306
|
for (const [name, path] of Object.entries(inst.props)) {
|
|
233
307
|
if (path === null)
|
|
234
|
-
throw new BuildError(
|
|
308
|
+
throw new BuildError(
|
|
309
|
+
'instance-prop-path',
|
|
310
|
+
`${ir.id}: child ${inst.child_id} prop ${name} is not a props path, a JSON literal or a template expression over props; compute it in a loader, or wrap the child's derived value in cache()`,
|
|
311
|
+
)
|
|
235
312
|
props[name] = path
|
|
236
313
|
}
|
|
237
|
-
|
|
314
|
+
const rec: ChildRecord = { id: inst.child_id, instances: literal ? 'per-row-literal' : list === undefined ? 'static' : `per-row:${list}`, props }
|
|
315
|
+
if (inst.literals && Object.keys(inst.literals).length > 0) rec.literals = { ...inst.literals }
|
|
316
|
+
if (inst.exprs && Object.keys(inst.exprs).length > 0) rec.exprs = { ...inst.exprs }
|
|
317
|
+
if (literal) rec.rows = inst.rows_literal
|
|
318
|
+
return rec
|
|
238
319
|
})
|
|
239
320
|
for (const id of clientOnly) out.push({ id, instances: 'static', props: {} })
|
|
240
321
|
return out
|
|
@@ -261,7 +342,8 @@ export function writeManifest(opts: {
|
|
|
261
342
|
leaves: FlatRoute[]
|
|
262
343
|
routeComponent: Map<string, string>
|
|
263
344
|
compiled: Map<string, Compiled>
|
|
264
|
-
runtime:
|
|
345
|
+
/** The runtime files `buildRuntime` wrote: the core and the feature modules. */
|
|
346
|
+
runtime: { core: string; features: Record<string, string>; nav?: string }
|
|
265
347
|
/** component id → `client/<file>` (client chunk or react island chunk). */
|
|
266
348
|
chunks: Map<string, string>
|
|
267
349
|
actions?: ActionsRecord | null
|
|
@@ -280,7 +362,10 @@ export function writeManifest(opts: {
|
|
|
280
362
|
needs_worker: jobs.length > 0,
|
|
281
363
|
use_id_slots: c.ir.use_id_slots ?? 0,
|
|
282
364
|
stores: storesOf(compiled, c.id),
|
|
365
|
+
features: featuresOf(c.id, c.jinja, tierOf(c.ir) === 'react'),
|
|
283
366
|
}
|
|
367
|
+
for (const f of components[c.id]!.features)
|
|
368
|
+
if (!(f in opts.runtime.features)) throw new BuildError('runtime-feature-missing', `${c.id}: needs the client runtime feature ${f}, which the build did not ship`)
|
|
284
369
|
}
|
|
285
370
|
const routes: RouteRecord[] = opts.leaves.map((l) => {
|
|
286
371
|
// A node without a Component (a pure path/loader group) renders nothing: not a chain entry.
|
|
@@ -302,7 +387,7 @@ export function writeManifest(opts: {
|
|
|
302
387
|
}
|
|
303
388
|
})
|
|
304
389
|
linkNativeChunks(routes, compiled, components)
|
|
305
|
-
return { version:
|
|
390
|
+
return { version: MANIFEST_VERSION, routes, components, assets: { runtime: opts.runtime.core, features: opts.runtime.features, ...(opts.runtime.nav === undefined ? {} : { nav: opts.runtime.nav }) }, jobs_module: 'jobs.js', actions: opts.actions ?? null }
|
|
306
391
|
}
|
|
307
392
|
|
|
308
393
|
/** S6 amendment (F66): the server's asset injection walks only each chain component's direct
|
|
@@ -315,6 +400,8 @@ function linkNativeChunks(routes: RouteRecord[], compiled: Map<string, Compiled>
|
|
|
315
400
|
const chainIds = new Set(routes.flatMap((r) => r.chain))
|
|
316
401
|
for (const root of chainIds) {
|
|
317
402
|
const rec = components[root]!
|
|
403
|
+
// A React root renders its whole subtree itself: whatever native chunks its IR lists are not served.
|
|
404
|
+
if (rec.tier === 'react') continue
|
|
318
405
|
const linked = new Set(rec.children.map((ch) => ch.id))
|
|
319
406
|
const seen = new Set([root])
|
|
320
407
|
const walk = (id: string) => {
|