@christianirack/brainllm-runtime 0.8.8 → 0.8.9

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 (2) hide show
  1. package/package.json +1 -1
  2. package/server/server.mjs +21 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christianirack/brainllm-runtime",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
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
@@ -193,7 +193,7 @@ const cacheSearch = url => {
193
193
  return kept.size ? '?' + kept : ''
194
194
  }
195
195
  // Messages this file writes itself, safe to log as the cause; anything else in an error message may hold private data.
196
- const OWN_CAUSES = new Set(['Backend request unavailable', 'Backend request timed out', 'Backend response exceeds its byte budget', 'Backend request capacity is unavailable', 'Backend stream capacity is unavailable', 'Native file request failed', 'Native file response is invalid', 'Native authentication is temporarily unavailable', 'Native resource response must be JSON', 'Native resource response is invalid', 'Native resource request failed', 'Invalid final response status', 'Invalid configured redirect location', 'timeout'])
196
+ const OWN_CAUSES = new Set(['Backend request unavailable', 'Backend request timed out', 'Backend response exceeds its byte budget', 'Backend request capacity is unavailable', 'Backend stream capacity is unavailable', 'Native file request failed', 'Native file response is invalid', 'Native resource response must be JSON', 'Native resource response is invalid', 'Native resource request failed', 'Invalid final response status', 'Invalid configured redirect location', 'timeout'])
197
197
  const CODE_SHAPE = /^[A-Z][A-Z0-9_]{0,63}$/
198
198
  const varyEncoding = v => (/accept-encoding/i.test(v || '') ? v : [v, 'Accept-Encoding'].filter(Boolean).join(', '))
199
199
  function bodyEncoding(req, status, headers) {
@@ -308,9 +308,9 @@ export function nativeAuthBridge(raw, refused, serviceKey, report, ownSession) {
308
308
  if (!user || typeof user.id !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(user.id) || typeof user.email !== 'string' || user.email.length > 254 || typeof user.role !== 'string' || !/^[a-z][a-z0-9_]{0,47}$/.test(user.role) || ['public','owner'].includes(user.role) || typeof user.verified !== 'boolean') throw new HttpError(503,'Native authentication response is unavailable')
309
309
  return Object.freeze({id:user.id,email:user.email,role:user.role,verified:user.verified})
310
310
  }
311
- const unavailable = () => new HttpError(503, 'Native authentication is temporarily unavailable')
311
+ const unavailable = (why = 'the backend did not answer') => new HttpError(503, `Native authentication is unavailable: ${why}`)
312
312
  const call = (incoming, response, path, method, rid, origin) => {
313
- if (stopped || active >= 8) return Promise.reject(unavailable())
313
+ if (stopped || active >= 8) return Promise.reject(unavailable(stopped ? 'the server is shutting down' : '8 session checks are already in flight'))
314
314
  const cookie = cookies(incoming, method === 'POST')
315
315
  const client = forwardedFor(incoming)
316
316
  active++
@@ -350,7 +350,7 @@ export function nativeAuthBridge(raw, refused, serviceKey, report, ownSession) {
350
350
  })
351
351
  })
352
352
  owned.add(outgoing); incoming.once('aborted', cancel); response.once('close', disconnected)
353
- timer = setTimeout(() => outgoing.destroy(unavailable()), 2000)
353
+ timer = setTimeout(() => outgoing.destroy(unavailable('the backend took more than 2 s')), 2000)
354
354
  outgoing.once('error', () => finish(true)); outgoing.end()
355
355
  } catch { outgoing?.destroy(); finish(true) }
356
356
  })
@@ -363,7 +363,7 @@ export function nativeAuthBridge(raw, refused, serviceKey, report, ownSession) {
363
363
  let origin
364
364
  try { origin = new URL(process.env.PUBLIC_SITE_URL).origin } catch { origin = 'https://' + (req.headers['x-forwarded-host'] || req.headers.host) }
365
365
  return new Promise((resolve, reject) => {
366
- if (stopped || active >= 8) return reject(unavailable())
366
+ if (stopped || active >= 8) return reject(unavailable(stopped ? 'the server is shutting down' : '8 session checks are already in flight'))
367
367
  active++
368
368
  let settled = false
369
369
  const done = (error, value) => { if (settled) return; settled = true; active--; clearTimeout(timer); error ? reject(unavailable()) : resolve(value) }
@@ -388,7 +388,7 @@ export function nativeAuthBridge(raw, refused, serviceKey, report, ownSession) {
388
388
  } catch { done(true) }
389
389
  })
390
390
  })
391
- const timer = setTimeout(() => outgoing.destroy(unavailable()), 2000)
391
+ const timer = setTimeout(() => outgoing.destroy(unavailable('the backend took more than 2 s')), 2000)
392
392
  owned.add(outgoing); outgoing.once('close', () => owned.delete(outgoing))
393
393
  outgoing.once('error', () => done(true)); outgoing.end()
394
394
  })
@@ -949,7 +949,8 @@ export function createServer(o = {}) {
949
949
  // log fields for a thrown value: its stack (sanitized by log) plus, for B09xx budget failures, the typed fields only
950
950
  const trace = e => {
951
951
  const out = { stack: String((e && e.stack) || e) }
952
- if (e instanceof HttpError && OWN_CAUSES.has(e.message)) out.cause = e.message
952
+ if (e instanceof HttpError && (OWN_CAUSES.has(e.message) || e.message.startsWith('Native authentication is unavailable:'))) out.cause = e.message
953
+ if (e && e.code === 'ERR_MODULE_NOT_FOUND') out.fix = 'this process runs an older build whose files are gone; restart it after brain build'
953
954
  if (e && CODE_SHAPE.test(e.transport || '')) out.transport = e.transport
954
955
  if (e && typeof e.code === 'string' && CODE_SHAPE.test(e.code)) out.backendCode = e.code
955
956
  if (e && e.kind === 'budget' && /^B09\d\d$/.test(e.code) && typeof e.budget === 'string' && /^[a-zA-Z]{1,16}$/.test(e.budget)) {
@@ -1706,6 +1707,15 @@ export function createServer(o = {}) {
1706
1707
  const href = tag && /\bhref="(https?:\/\/[^"<>\s]+)"/.exec(tag)?.[1].replaceAll('&amp;', '&')
1707
1708
  return href && URL.canParse(href) && !/\/[:*]/.test(new URL(href).pathname) ? { link: `<${href}>; rel="canonical"` } : {}
1708
1709
  }
1710
+ // What exists whatever the routes say: generated sitemaps, built files and the icon browsers ask for by habit.
1711
+ async function serveFixed(req, res, url) {
1712
+ if (DYN.length && /^\/sitemap(-static|-dynamic(-\d+)?)?\.xml$/.test(url.pathname) && (await serveSitemap(req, res, url))) return true
1713
+ if (serveStatic(req, res, url)) return true
1714
+ if (url.pathname !== '/favicon.ico' || !file('favicon.svg')) return false
1715
+ send(res, 301, '', { location: withBase('/favicon.svg'), 'cache-control': 'public, max-age=86400' })
1716
+ return true
1717
+ }
1718
+
1709
1719
  function serveStatic(req, res, url) {
1710
1720
  const p = publicStaticPath(url.pathname)
1711
1721
  if (p === null) return false
@@ -1867,6 +1877,8 @@ export function createServer(o = {}) {
1867
1877
  if (!m) continue
1868
1878
  // a form page without load is prerendered: GET serves that file (build ETag); only its POST needs the server
1869
1879
  if (!post && route.forms && serveStatic(req, res, url)) return
1880
+ // a parameter never hides a real file: `/[code]` must not swallow /favicon.svg, /sitemap.xml or a prerendered /login
1881
+ if (!post && m.groups && (await serveFixed(req, res, url))) return
1870
1882
  if (!post && serveTwin(req, res, url)) return
1871
1883
  const paged = post ? null : pageParameter(route, url)
1872
1884
  if (paged === 404) return notFound(req, res)
@@ -1874,15 +1886,13 @@ export function createServer(o = {}) {
1874
1886
  return await (post ? formPost : ssr)(req, res, route, m.groups ? { ...m.groups } : {}, url, rid)
1875
1887
  }
1876
1888
  if (post) return send(res, 405, 'Method not allowed', { allow: 'GET, HEAD' })
1877
- if (DYN.length && /^\/sitemap(-static|-dynamic(-\d+)?)?\.xml$/.test(url.pathname) && (await serveSitemap(req, res, url))) return
1878
- if (serveStatic(req, res, url)) return
1879
- // browsers still ask for /favicon.ico: the app's SVG icon answers it
1880
- if (url.pathname === '/favicon.ico' && file('favicon.svg')) return send(res, 301, '', { location: withBase('/favicon.svg'), 'cache-control': 'public, max-age=86400' })
1889
+ if (await serveFixed(req, res, url)) return
1881
1890
  if (isProbe(url.pathname)) return await answerProbe(req, res, url, rid)
1882
1891
  notFound(req, res)
1883
1892
  } catch (e) {
1884
1893
  const busy = e instanceof HttpError && e.code === 'BRIDGE_BUSY'
1885
1894
  if (busy) log('warn', 'backend bridge busy', { rid, path: req.$brainPath || String(req.url || '').split('?')[0], fix: e.fix })
1895
+ else if (e instanceof HttpError && OWN_CAUSES.has(e.message)) log('warn', 'backend unavailable', { rid, path: req.$brainPath || String(req.url || '').split('?')[0], status: e.status, cause: e.message, ...(CODE_SHAPE.test(e.transport || '') ? { transport: e.transport } : {}) })
1886
1896
  else log('error', 'unhandled', { rid, path: req.url, ...trace(e) })
1887
1897
  try {
1888
1898
  if (busy && !res.headersSent) { res.setHeader('retry-after', String(e.retryAfter)); failPage(res, 503, rid, { code: e.code, message: 'Service Unavailable', fix: e.fix }) }