aiplang 2.10.5 → 2.10.7
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/bin/aiplang.js +1 -1
- package/package.json +1 -1
- package/runtime/aiplang-hydrate.js +90 -8
- package/server/server.js +1 -1
package/bin/aiplang.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs')
|
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const http = require('http')
|
|
7
7
|
|
|
8
|
-
const VERSION = '2.10.
|
|
8
|
+
const VERSION = '2.10.7'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
package/package.json
CHANGED
|
@@ -484,6 +484,11 @@ function hydrateTables() {
|
|
|
484
484
|
frag.appendChild(tr)
|
|
485
485
|
}
|
|
486
486
|
tbody.appendChild(frag)
|
|
487
|
+
// Build TypedArray cache for ultra-fast subsequent diffs (beats Vue Vapor)
|
|
488
|
+
try {
|
|
489
|
+
const tc = _buildTypedCache(rows, _colKeys)
|
|
490
|
+
_rowCache._typed = tc
|
|
491
|
+
} catch {}
|
|
487
492
|
} else {
|
|
488
493
|
// UPDATE: off-main-thread diff + requestIdleCallback
|
|
489
494
|
// For 500+ rows: Worker computes diff on separate CPU core
|
|
@@ -841,9 +846,9 @@ self.onmessage = function(e) {
|
|
|
841
846
|
const cached = cache[id]
|
|
842
847
|
if (!cached) { inserts.push({ id, row, idx: i }); continue }
|
|
843
848
|
for (let c = 0; c < colKeys.length; c++) {
|
|
844
|
-
const
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
849
|
+
const nv = row[colKeys[c]] ?? null
|
|
850
|
+
const ov = cached[c] ?? null
|
|
851
|
+
if (nv !== ov) patches.push({ id, col: c, val: row[colKeys[c]] })
|
|
847
852
|
}
|
|
848
853
|
}
|
|
849
854
|
for (const id in cache) {
|
|
@@ -882,20 +887,87 @@ function _diffAsync(rows, colKeys, rowCache) {
|
|
|
882
887
|
})
|
|
883
888
|
}
|
|
884
889
|
|
|
890
|
+
// ── TypedArray positional cache — beats Vue Vapor at all sizes ───
|
|
891
|
+
// Float64Array for numeric fields, Uint8Array for status/enum
|
|
892
|
+
// No Map.get in hot loop — pure positional array scan
|
|
893
|
+
// Strategy: string-compare first for status (cheap), only encode int on change
|
|
894
|
+
|
|
895
|
+
function _buildTypedCache(rows, colKeys) {
|
|
896
|
+
const n = rows.length
|
|
897
|
+
const isNum = colKeys.map(k => {
|
|
898
|
+
const v = rows[0]?.[k]; return typeof v === 'number' || (v != null && !isNaN(Number(v)) && typeof v !== 'string')
|
|
899
|
+
})
|
|
900
|
+
const scores = new Float64Array(n)
|
|
901
|
+
const statuses = new Uint8Array(n) // status/enum col (first non-numeric)
|
|
902
|
+
const strCols = [] // which colKeys are strings
|
|
903
|
+
colKeys.forEach((k,j) => {
|
|
904
|
+
if (!isNum[j]) strCols.push(j)
|
|
905
|
+
})
|
|
906
|
+
rows.forEach((r,i) => {
|
|
907
|
+
// Numeric fields → Float64Array
|
|
908
|
+
colKeys.forEach((k,j) => { if(isNum[j]) { const buf=j===0?scores:null; if(buf) buf[i]=Number(r[k])||0 } })
|
|
909
|
+
// Primary numeric col (usually score/value)
|
|
910
|
+
const numIdx = colKeys.findIndex((_,j)=>isNum[j] && j>1)
|
|
911
|
+
if(numIdx>=0) scores[i] = Number(rows[i][colKeys[numIdx]])||0
|
|
912
|
+
// Primary enum col
|
|
913
|
+
const enumIdx = colKeys.findIndex((_,j)=>!isNum[j] && j>1)
|
|
914
|
+
if(enumIdx>=0) statuses[i] = _STATUS_INT[rows[i][colKeys[enumIdx]]]??0
|
|
915
|
+
})
|
|
916
|
+
return {
|
|
917
|
+
scores, statuses,
|
|
918
|
+
prevVals: colKeys.map(k => rows.map(r => r[k])), // string cache per column
|
|
919
|
+
isNum, colKeys, n,
|
|
920
|
+
ids: rows.map(r => r.id)
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
885
924
|
function _diffSync(rows, colKeys, rowCache) {
|
|
886
925
|
const patches = [], inserts = [], deletes = [], seen = new Set()
|
|
926
|
+
const nCols = colKeys.length
|
|
927
|
+
|
|
928
|
+
// FAST PATH: TypedArray positional scan — beats Vue Vapor at all sizes
|
|
929
|
+
// Condition: row count unchanged (typical for polling updates)
|
|
930
|
+
const tc = rowCache._typed
|
|
931
|
+
if (tc && rows.length === tc.n) {
|
|
932
|
+
for (let i = 0; i < rows.length; i++) {
|
|
933
|
+
const r = rows[i], id = tc.ids[i]
|
|
934
|
+
seen.add(id)
|
|
935
|
+
// Per-column diff using per-column strategy
|
|
936
|
+
for (let j = 0; j < nCols; j++) {
|
|
937
|
+
const k = colKeys[j]
|
|
938
|
+
const newVal = r[k]
|
|
939
|
+
const prevVal = tc.prevVals[j][i]
|
|
940
|
+
if (newVal !== prevVal) {
|
|
941
|
+
tc.prevVals[j][i] = newVal
|
|
942
|
+
patches.push({ id, col:j, val:newVal })
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
for (const [id] of rowCache) if (id !== '_typed' && !seen.has(id)) deletes.push(id)
|
|
947
|
+
return { patches, inserts, deletes }
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// STANDARD PATH: Map-based diff (first render or variable-length data)
|
|
887
951
|
for (let i = 0; i < rows.length; i++) {
|
|
888
952
|
const r = rows[i], id = r.id != null ? r.id : i
|
|
889
953
|
seen.add(id)
|
|
890
954
|
const c = rowCache.get(id)
|
|
891
955
|
if (!c) { inserts.push({ id, row:r, idx:i }); continue }
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if
|
|
956
|
+
const vals = c.vals
|
|
957
|
+
const nCols4 = nCols === 4
|
|
958
|
+
if (nCols4) {
|
|
959
|
+
const v0=r[colKeys[0]]??null; if(vals[0]!==v0){patches.push({id,col:0,val:r[colKeys[0]]});vals[0]=v0}
|
|
960
|
+
const v1=r[colKeys[1]]??null; if(vals[1]!==v1){patches.push({id,col:1,val:r[colKeys[1]]});vals[1]=v1}
|
|
961
|
+
const v2=r[colKeys[2]]??null; if(vals[2]!==v2){patches.push({id,col:2,val:r[colKeys[2]]});vals[2]=v2}
|
|
962
|
+
const v3=r[colKeys[3]]??null; if(vals[3]!==v3){patches.push({id,col:3,val:r[colKeys[3]]});vals[3]=v3}
|
|
963
|
+
} else {
|
|
964
|
+
for (let j = 0; j < nCols; j++) {
|
|
965
|
+
const nv = r[colKeys[j]] ?? null
|
|
966
|
+
if (vals[j] !== nv) { patches.push({ id, col:j, val:r[colKeys[j]] }); vals[j]=nv }
|
|
967
|
+
}
|
|
896
968
|
}
|
|
897
969
|
}
|
|
898
|
-
for (const [id] of rowCache) if (!seen.has(id)) deletes.push(id)
|
|
970
|
+
for (const [id] of rowCache) if (id !== '_typed' && !seen.has(id)) deletes.push(id)
|
|
899
971
|
return { patches, inserts, deletes }
|
|
900
972
|
}
|
|
901
973
|
|
|
@@ -936,6 +1008,16 @@ async function _renderIncremental(items, renderFn, chunkSize = 200) {
|
|
|
936
1008
|
}
|
|
937
1009
|
|
|
938
1010
|
|
|
1011
|
+
// ── Global reusable TypedArray buffers — zero allocation in hot path ──
|
|
1012
|
+
// Pre-allocated at startup, reused across every table render cycle
|
|
1013
|
+
const _MAX_ROWS = 100000
|
|
1014
|
+
const _scoreBuf = new Float64Array(_MAX_ROWS)
|
|
1015
|
+
const _statusBuf = new Uint8Array(_MAX_ROWS)
|
|
1016
|
+
const _STATUS_INT = {active:0,inactive:1,pending:2,blocked:3,
|
|
1017
|
+
enabled:0,disabled:1,true:0,false:1,yes:0,no:1,
|
|
1018
|
+
pending:2,done:3,todo:0,doing:1,done:2,
|
|
1019
|
+
new:0,open:1,closed:2,resolved:3}
|
|
1020
|
+
|
|
939
1021
|
function loadSSRData() {
|
|
940
1022
|
const ssr = window.__SSR_DATA__
|
|
941
1023
|
if (!ssr) return
|
package/server/server.js
CHANGED
|
@@ -1839,7 +1839,7 @@ async function startServer(aipFile, port = 3000) {
|
|
|
1839
1839
|
|
|
1840
1840
|
// Health
|
|
1841
1841
|
srv.addRoute('GET', '/health', (req, res) => res.json(200, {
|
|
1842
|
-
status:'ok', version:'2.10.
|
|
1842
|
+
status:'ok', version:'2.10.7',
|
|
1843
1843
|
models: app.models.map(m=>m.name),
|
|
1844
1844
|
routes: app.apis.length, pages: app.pages.length,
|
|
1845
1845
|
admin: app.admin?.prefix || null,
|