@christianirack/brainllm-runtime 0.8.11 → 0.9.0
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-links.js +4 -2
- package/brain-list.js +9 -0
- package/brain-prefetch-shared.js +1 -1
- package/brain-router.js +11 -4
- package/brain-text.js +56 -0
- package/brain-url.js +36 -3
- package/brain.js +14 -8
- package/package.json +1 -1
- package/server/server.mjs +371 -72
- package/shim/dom.js +8 -17
- package/sw/brain-sw.js +57 -3
- package/templates/apple-signin.js +117 -0
- package/ui/dialog.brain +2 -2
package/brain-links.js
CHANGED
|
@@ -14,8 +14,10 @@ export default function links(lang, b, p, re, s) {
|
|
|
14
14
|
const t = tables[document.documentElement.getAttribute('lang')]
|
|
15
15
|
if (!t || !h || h[0] !== '/' || h[1] === '/') return h
|
|
16
16
|
const a = t.b && (h + '/').startsWith(t.b + '/') ? h.slice(t.b.length) || '/' : h
|
|
17
|
-
const m = /^[^?#]*/.exec(a)[0]
|
|
18
|
-
|
|
17
|
+
const m = /^[^?#]*/.exec(a)[0], key = m.replace(/\/+$/, '') || '/'
|
|
18
|
+
if (key === t.p || key.startsWith(t.p + '/')) return h
|
|
19
|
+
let n = t.s[key] || (t.re.test(key) && (key === '/' ? t.p : t.p + key))
|
|
20
|
+
if (n && key !== '/' && m.endsWith('/') && !n.endsWith('/')) n += '/'
|
|
19
21
|
return n ? t.b + n + a.slice(m.length) : h
|
|
20
22
|
}
|
|
21
23
|
G.__brainHref = to
|
package/brain-list.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Pure bounded integer iteration, outside the runtime core.
|
|
2
|
+
export function range(from, to) {
|
|
3
|
+
if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to)) throw new RangeError('range requires two safe integer endpoints')
|
|
4
|
+
const length = Math.max(0, to - from)
|
|
5
|
+
if (length > 10000) throw new RangeError('range cannot emit more than 10000 integers')
|
|
6
|
+
const values = new Array(length)
|
|
7
|
+
for (let i = 0; i < length; i++) values[i] = from + i
|
|
8
|
+
return values
|
|
9
|
+
}
|
package/brain-prefetch-shared.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function prefetchPage(url, signal) {
|
|
|
29
29
|
let hit
|
|
30
30
|
while ((hit = pattern.exec(text)) && assets.length < 16) {
|
|
31
31
|
const asset = new URL(hit[1] || hit[2] || hit[3], base)
|
|
32
|
-
if (asset.origin === base.origin && asset.pathname.includes('/_b/')) assets.push(asset.href)
|
|
32
|
+
if (asset.origin === base.origin && (asset.pathname.includes('/_b/') || /\/[a-z0-9]{8}\.(?:js|mjs|jsbrain|css|cssbrain)$/.test(asset.pathname))) assets.push(asset.href)
|
|
33
33
|
}
|
|
34
34
|
return { ok: true, status: r.status, url: r.url, headers: r.headers, text, bytes, assets }
|
|
35
35
|
} catch { return null }
|
package/brain-router.js
CHANGED
|
@@ -96,6 +96,10 @@ export function router(o = {}) {
|
|
|
96
96
|
const region = o.region || 'main'
|
|
97
97
|
const parse = o.parse || (h => new DOMParser().parseFromString(h, 'text/html'))
|
|
98
98
|
const fullLoad = new Set((o.fullLoad || []).map(p => new URL(p, location.href).pathname.replace(/\/+$/, '') || '/'))
|
|
99
|
+
// COOP belongs to the document, not the swapped view. Only real head markers opt into provider popups.
|
|
100
|
+
const popup = d => [...((d.head || d.querySelector('head'))?.querySelectorAll('meta') || [])].some(m =>
|
|
101
|
+
/^(brain-google-signin|brain-apple-signin)$/i.test(m.getAttribute('name') || '') && m.getAttribute('content') === '1')
|
|
102
|
+
const popupDocument = popup(document)
|
|
99
103
|
let seq = 0, through = null, stale = false
|
|
100
104
|
const ahead = predict(o, (u, p = true) => same(u, p))
|
|
101
105
|
// Build signature (§35.7): the page's <meta name="brain-build">. A navigation response from another build, or the
|
|
@@ -205,6 +209,7 @@ export function router(o = {}) {
|
|
|
205
209
|
if (!r.ok || !/html/.test(hd('content-type') || 'html')) throw Error(r.status)
|
|
206
210
|
const doc = parse(await r.text())
|
|
207
211
|
if (my !== seq) return // a newer navigation won
|
|
212
|
+
if (popup(doc) !== popupDocument) return hard(url) // entering or leaving a popup page needs fresh HTTP headers
|
|
208
213
|
// another language (§37): a full load brings its own dictionaries, `lang` and `dir`; locales never mix in memory
|
|
209
214
|
const lang = d => d?.documentElement?.getAttribute('lang')
|
|
210
215
|
if (lang(doc) && lang(document) && lang(doc) !== lang(document)) return hard(url)
|
|
@@ -308,7 +313,7 @@ export function router(o = {}) {
|
|
|
308
313
|
const h = e => {
|
|
309
314
|
if (through && e.destination.url === through) return void (through = null)
|
|
310
315
|
if (e.navigationType === 'reload') return // a reload is always a real full load (e.g. after a deploy deleted a chunk)
|
|
311
|
-
if (!e.canIntercept || e.hashChange || e.downloadRequest || e.formData || !same(e.destination.url)) return
|
|
316
|
+
if (!e.canIntercept || e.hashChange || e.downloadRequest || e.formData || externalLink(e.sourceElement) || !same(e.destination.url)) return
|
|
312
317
|
if (e.cancelable && !leave()) return e.preventDefault()
|
|
313
318
|
const to = e.destination.url, from = nav.currentEntry?.url
|
|
314
319
|
if (G.__brainURLOnly?.(from || location.href, to)) return e.intercept({ focusReset: 'manual', handler: () => G.__brainURLRestore?.() })
|
|
@@ -362,7 +367,7 @@ export function router(o = {}) {
|
|
|
362
367
|
if (e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
|
|
363
368
|
let a = e.target
|
|
364
369
|
while (a && a.tagName !== 'A') a = a.parentNode
|
|
365
|
-
if (!a || !a.getAttribute('href') || a.getAttribute('target') || a.hasAttribute('download') || !same(a.href || a.getAttribute('href'))) return
|
|
370
|
+
if (!a || !a.getAttribute('href') || a.getAttribute('target') || a.hasAttribute('download') || externalLink(a) || !same(a.href || a.getAttribute('href'))) return
|
|
366
371
|
const u = new URL(a.href || a.getAttribute('href'), location.href)
|
|
367
372
|
if (u.pathname === location.pathname && u.hash) return // in-page anchor: native
|
|
368
373
|
e.preventDefault()
|
|
@@ -416,7 +421,7 @@ export function predict(o = {}, same = () => false) {
|
|
|
416
421
|
return !NOPE.test(u.pathname) && !FILE.test(u.pathname) && !SECRET.test(u.search)
|
|
417
422
|
}
|
|
418
423
|
const ok = a => {
|
|
419
|
-
if (!a?.getAttribute?.('href') || a.getAttribute('target') || a.hasAttribute('download')) return null
|
|
424
|
+
if (!a?.getAttribute?.('href') || a.getAttribute('target') || a.hasAttribute('download') || externalLink(a)) return null
|
|
420
425
|
for (let n = a; n?.getAttribute; n = n.parentNode) if (n.getAttribute('data-prefetch') === 'false') return null
|
|
421
426
|
const k = key(a.href || a.getAttribute('href'))
|
|
422
427
|
return safe(k) ? k : null
|
|
@@ -426,7 +431,7 @@ export function predict(o = {}, same = () => false) {
|
|
|
426
431
|
let n = 0
|
|
427
432
|
for (const href of list) {
|
|
428
433
|
const u = key(href)
|
|
429
|
-
if (!u || pre.has(u) || pre.size > 47 || !same(u) || !new URL(u).pathname.includes('/_b/') || ++n > 8) continue
|
|
434
|
+
if (!u || pre.has(u) || pre.size > 47 || !same(u) || !(new URL(u).pathname.includes('/_b/') || /\/[a-z0-9]{8}\.(?:js|mjs|jsbrain|css|cssbrain)$/.test(new URL(u).pathname)) || ++n > 8) continue
|
|
430
435
|
const l = document.createElement('link')
|
|
431
436
|
l.setAttribute('rel', /\.(js|jsbrain)(\?|$)/.test(u) ? 'modulepreload' : 'prefetch')
|
|
432
437
|
l.setAttribute('href', u)
|
|
@@ -619,3 +624,5 @@ function hold(el) {
|
|
|
619
624
|
},
|
|
620
625
|
})
|
|
621
626
|
}
|
|
627
|
+
// An absolute navigation can intentionally belong to another site on the same origin.
|
|
628
|
+
const externalLink = a => (a?.getAttribute?.('rel') || '').split(/\s+/).some(token => token.toLowerCase() === 'external') && /^https?:\/\//i.test((a?.getAttribute?.('href') || '').trim())
|
package/brain-text.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Pure bounded Unicode matching; no locale, network, DOM or persistent state.
|
|
2
|
+
const letters = value => {
|
|
3
|
+
if (typeof value !== 'string' || value.length > 8192) throw new RangeError('text helpers require strings of at most 4096 Unicode characters')
|
|
4
|
+
const result = Array.from(value)
|
|
5
|
+
if (result.length > 4096) throw new RangeError('text helpers require strings of at most 4096 Unicode characters')
|
|
6
|
+
return result
|
|
7
|
+
}
|
|
8
|
+
const fold = value => {
|
|
9
|
+
letters(value)
|
|
10
|
+
const result = value.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
|
|
11
|
+
letters(result)
|
|
12
|
+
return result
|
|
13
|
+
}
|
|
14
|
+
// Optimal string alignment: adjacent transpositions count as one edit; the band and row minimum bound work.
|
|
15
|
+
const distance = (left, right, max = 2) => {
|
|
16
|
+
if (!Number.isInteger(max) || max < 0 || max > 64) throw new RangeError('text.distance max must be an integer from 0 to 64')
|
|
17
|
+
const a = letters(left), b = letters(right), n = a.length, m = b.length, cap = max + 1
|
|
18
|
+
if (Math.abs(n - m) > max) return cap
|
|
19
|
+
if (!n) return Math.min(m, cap)
|
|
20
|
+
if (!m) return Math.min(n, cap)
|
|
21
|
+
let before = new Uint16Array(m + 1).fill(cap)
|
|
22
|
+
let previous = new Uint16Array(m + 1).fill(cap)
|
|
23
|
+
let current = new Uint16Array(m + 1).fill(cap)
|
|
24
|
+
for (let j = 0; j <= Math.min(m, max); j++) previous[j] = j
|
|
25
|
+
for (let i = 1; i <= n; i++) {
|
|
26
|
+
const low = Math.max(1, i - max), high = Math.min(m, i + max)
|
|
27
|
+
current[0] = Math.min(i, cap)
|
|
28
|
+
if (low > 1) current[low - 1] = cap
|
|
29
|
+
if (high < m) current[high + 1] = cap
|
|
30
|
+
let minimum = current[0]
|
|
31
|
+
for (let j = low; j <= high; j++) {
|
|
32
|
+
let value = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1), cap)
|
|
33
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) value = Math.min(value, before[j - 2] + 1)
|
|
34
|
+
current[j] = value
|
|
35
|
+
minimum = Math.min(minimum, value)
|
|
36
|
+
}
|
|
37
|
+
if (minimum > max) return cap
|
|
38
|
+
const spare = before; before = previous; previous = current; current = spare
|
|
39
|
+
}
|
|
40
|
+
return Math.min(previous[m], cap)
|
|
41
|
+
}
|
|
42
|
+
const fuzzy = (word, list) => {
|
|
43
|
+
const needle = fold(word), size = letters(needle).length
|
|
44
|
+
if (!Array.isArray(list) || list.length > 10000) throw new RangeError('text.fuzzy requires at most 10000 candidate strings')
|
|
45
|
+
const count = list.length, max = size < 4 ? 0 : size < 8 ? 1 : 2
|
|
46
|
+
const found = []
|
|
47
|
+
let units = 0
|
|
48
|
+
for (let i = 0; i < count; i++) {
|
|
49
|
+
const candidate = list[i]
|
|
50
|
+
if (typeof candidate !== 'string' || candidate.length > 1048576 - units) throw new RangeError('text.fuzzy candidate text exceeds 1048576 UTF-16 code units or contains a non-string')
|
|
51
|
+
units += candidate.length
|
|
52
|
+
if (distance(needle, fold(candidate), max) <= max) found.push(candidate)
|
|
53
|
+
}
|
|
54
|
+
return found
|
|
55
|
+
}
|
|
56
|
+
export const text = Object.freeze({ fold, distance, fuzzy })
|
package/brain-url.js
CHANGED
|
@@ -45,10 +45,35 @@ export function urlState(initial, options) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// A native UI primitive stays stateless in author source; its DOM adapter owns navigation state and cleanup.
|
|
48
|
-
export function urlDialog(element, key) {
|
|
49
|
-
if (!element ||
|
|
48
|
+
export function urlDialog(element, key, options) {
|
|
49
|
+
if (!element || G.__brainSSR || G.__brainRoute || !G.location) return
|
|
50
50
|
return I.root(dispose => {
|
|
51
51
|
let alive = true
|
|
52
|
+
if (options?.dismissible === true) {
|
|
53
|
+
let startedOutside = false
|
|
54
|
+
const outside = event => {
|
|
55
|
+
if (event.target !== element || !element.open) return false
|
|
56
|
+
const bounds = element.getBoundingClientRect()
|
|
57
|
+
return event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom
|
|
58
|
+
}
|
|
59
|
+
const down = event => { startedOutside = !event.defaultPrevented && event.button === 0 && event.isPrimary !== false && outside(event) }
|
|
60
|
+
const cancel = () => { startedOutside = false }
|
|
61
|
+
const click = event => {
|
|
62
|
+
const dismiss = alive && !event.defaultPrevented && startedOutside && event.button === 0 && outside(event)
|
|
63
|
+
startedOutside = false
|
|
64
|
+
if (dismiss) { event.preventDefault(); event.stopPropagation(); element.close() }
|
|
65
|
+
}
|
|
66
|
+
element.addEventListener('pointerdown', down)
|
|
67
|
+
element.addEventListener('pointercancel', cancel)
|
|
68
|
+
element.addEventListener('click', click)
|
|
69
|
+
fx(() => () => {
|
|
70
|
+
alive = false
|
|
71
|
+
element.removeEventListener('pointerdown', down)
|
|
72
|
+
element.removeEventListener('pointercancel', cancel)
|
|
73
|
+
element.removeEventListener('click', click)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
if (!key) return dispose
|
|
52
77
|
const opened = urlState(!!element.open, {key})
|
|
53
78
|
const read = () => { if (alive) opened.v = !!element.open }
|
|
54
79
|
const apply = value => {
|
|
@@ -59,7 +84,15 @@ export function urlDialog(element, key) {
|
|
|
59
84
|
fx(() => {
|
|
60
85
|
const value = opened.v
|
|
61
86
|
if (element.isConnected) apply(value)
|
|
62
|
-
else
|
|
87
|
+
else {
|
|
88
|
+
const wasOpen = !!element.open
|
|
89
|
+
queueMicrotask(() => {
|
|
90
|
+
// An exposed open() may run immediately after mount, before this initial deferred sync.
|
|
91
|
+
// Preserve that newer DOM action instead of applying the old initial state over it.
|
|
92
|
+
if (alive && !!element.open !== wasOpen) read()
|
|
93
|
+
else apply(value)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
63
96
|
})
|
|
64
97
|
const observer = G.MutationObserver ? new G.MutationObserver(read) : null
|
|
65
98
|
observer?.observe(element, {attributes:true,attributeFilter:['open']})
|
package/brain.js
CHANGED
|
@@ -350,20 +350,26 @@ export function _ev(e) {
|
|
|
350
350
|
// a data property, not a getter: V8 keeps accessors in the event's shared hidden class, so a getter closure
|
|
351
351
|
// would keep the first event (and its component) alive for the life of the page
|
|
352
352
|
Object.defineProperty(e, 'currentTarget', { configurable: true, value: n })
|
|
353
|
-
|
|
354
|
-
// n moves on with the loop: keep the element for a later rejection
|
|
355
|
-
const r = h.call(n, e), el = n
|
|
356
|
-
if (r && r.then) r.then(null, x => oops(x, el, 'on:' + e.type)) // async handler: a rejected await too
|
|
357
|
-
} catch (x) {
|
|
358
|
-
oops(x, n, 'on:' + e.type)
|
|
359
|
-
}
|
|
353
|
+
eventCall(n, h, e)
|
|
360
354
|
if (e.cancelBubble) break
|
|
361
355
|
}
|
|
362
356
|
}
|
|
363
357
|
})
|
|
364
358
|
}
|
|
359
|
+
function eventCall(el, h, e) {
|
|
360
|
+
try {
|
|
361
|
+
const r = h.call(el, e)
|
|
362
|
+
if (r && r.then) r.then(null, x => oops(x, el, 'on:' + e.type))
|
|
363
|
+
} catch (x) { oops(x, el, 'on:' + e.type) }
|
|
364
|
+
}
|
|
365
365
|
export function on(el, t, h) {
|
|
366
|
-
if (/^(focus|blur|mouseenter|mouseleave|load|error|scroll)$/.test(t))
|
|
366
|
+
if (/^(focus|blur|mouseenter|mouseleave|pointerenter|pointerleave|load|error|scroll|scrollend|resize|close|cancel|toggle|beforetoggle|invalid|abort|canplay|canplaythrough|durationchange|emptied|ended|loadeddata|loadedmetadata|loadstart|pause|play|playing|progress|ratechange|seeked|seeking|stalled|suspend|timeupdate|volumechange|waiting|encrypted|waitingforkey)$/.test(t)) {
|
|
367
|
+
const listener = e => batch(() => eventCall(el, h, e))
|
|
368
|
+
return fx(() => {
|
|
369
|
+
el.addEventListener(t, listener)
|
|
370
|
+
return () => el.removeEventListener(t, listener)
|
|
371
|
+
})
|
|
372
|
+
}
|
|
367
373
|
el['$' + t] = h
|
|
368
374
|
document.addEventListener(t, _ev) // once per type: the DOM ignores the same listener added again
|
|
369
375
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christianirack/brainllm-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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": {
|