@christianirack/brainllm-runtime 0.8.0 → 0.8.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/brain-form.js CHANGED
@@ -35,7 +35,10 @@ const go = u => {
35
35
  // submitted does not ask (#299): the user chose to save, and a submit() that navigates once the server accepted the
36
36
  // data (`navigate('/products/' + p.id)`) leaves before submit() returns and marks the values saved.
37
37
  const DIRTY = (globalThis.__brainDirty ||= new Set())
38
- if (globalThis.addEventListener && !globalThis.__brainDirtyHook) {
38
+ // registered with the first edit, not at load: a `beforeunload` listener can keep a page out of the back/forward cache,
39
+ // and most pages that load this file are never edited
40
+ const guardUnload = () => {
41
+ if (!globalThis.addEventListener || globalThis.__brainDirtyHook) return
39
42
  globalThis.__brainDirtyHook = 1
40
43
  globalThis.addEventListener('beforeunload', e => {
41
44
  for (const f of DIRTY) if (f.dirty && !f.busy) return void (e.preventDefault(), (e.returnValue = ''))
@@ -315,6 +318,8 @@ function createForm(schema, o, readRule, validateRule, remote) {
315
318
  },
316
319
  }
317
320
  DIRTY.add(f)
321
+ const watch = () => fx(() => void (f.dirty && guardUnload()))
322
+ owner ? I.root(watch, owner) : watch()
318
323
  if (owner) I.root(() => fx(() => () => DIRTY.delete(f)), owner)
319
324
  return f
320
325
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christianirack/brainllm-runtime",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "The BRAINLLM runtime for the Rust-built web compiler for LLMs: dependency-free ES modules (signals, DOM, router, data, forms, channels). The brain binary embeds this same version.",
5
5
  "type": "module",
6
6
  "exports": {
package/server/server.mjs CHANGED
@@ -1129,8 +1129,17 @@ export function createServer(o = {}) {
1129
1129
  const loadOutcome = e => {
1130
1130
  if (e && e.redirect) return send(res, e.status || 303, '', { location: withBase(linkTo(route, redirect(e.redirect, e.status ?? 303).redirect)), ...ctx.session.headers() })
1131
1131
  if ((e instanceof HttpError && e.status === 404) || (e && e.notFound === true)) return missing(req, res, e) // `notFound()` 404 / `gone()` 410
1132
+ // the backend or another service said "not now": a temporary 503 tells crawlers to come back, a 500 tells them it broke
1133
+ // a status the page chose itself (`new HttpError(429)`) stays as it is, with the same hint to come back
1134
+ if (e && (e.status === 429 || e.status === 503) && !(e instanceof HttpError) && !res.headersSent) {
1135
+ log('warn', 'load temporarily unavailable', { rid, path: url.pathname, status: 503, ...trace(e) })
1136
+ res.setHeader('retry-after', String(Math.min(Math.ceil(e.retryAfter) || 5, 3600)))
1137
+ res.setHeader('cache-control', 'no-store')
1138
+ return errorPage(req, res, route, params, url, rid, 503)
1139
+ }
1132
1140
  const status = intendedStatus(e) || 500
1133
1141
  log('error', 'load failed', { rid, path: url.pathname, status, ...trace(e) })
1142
+ if ((status === 429 || status === 503) && !res.headersSent) res.setHeader('retry-after', String(Math.min(Math.ceil(e.retryAfter) || 5, 3600)))
1134
1143
  return status === 500 ? errorPage(req, res, route, params, url, rid) : failPage(res, status, rid)
1135
1144
  }
1136
1145
  if (route.stream && route.server && !fixed && !forms && status === 200 && !res.$capture) return streamed(req, res, route, params, url, rid, ctx, keep(extra || {}), loadOutcome)
@@ -1272,9 +1281,9 @@ export function createServer(o = {}) {
1272
1281
  return failPage(res, 403, rid)
1273
1282
  }
1274
1283
  // a failing load or guard → the folder's _error.brain (route `error`, props { message, rid }), else the generic page
1275
- function errorPage(req, res, route, params, url, rid) {
1276
- if (wantsHtml(req) && route.error) return ssr(req, res, route.error, params, url, rid, null, 500, { message: 'Something went wrong.', rid })
1277
- return failPage(res, 500, rid)
1284
+ function errorPage(req, res, route, params, url, rid, status = 500) {
1285
+ if (wantsHtml(req) && route.error) return ssr(req, res, route.error, params, url, rid, null, status, { message: 'Something went wrong.', rid })
1286
+ return failPage(res, status, rid)
1278
1287
  }
1279
1288
  async function render(route, props, forms, at = null) {
1280
1289
  const { install } = await load('_server/dom.js')