aiplang 2.10.6 → 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 +76 -10
- 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
|
|
@@ -882,24 +887,75 @@ 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()
|
|
887
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)
|
|
888
951
|
for (let i = 0; i < rows.length; i++) {
|
|
889
952
|
const r = rows[i], id = r.id != null ? r.id : i
|
|
890
953
|
seen.add(id)
|
|
891
954
|
const c = rowCache.get(id)
|
|
892
955
|
if (!c) { inserts.push({ id, row:r, idx:i }); continue }
|
|
893
956
|
const vals = c.vals
|
|
894
|
-
|
|
895
|
-
if (
|
|
896
|
-
const v0=r[colKeys[0]]??null; if(vals[0]!==v0){patches.push({id,col:0,val:r[colKeys[0]]});vals[0]=v0}
|
|
897
|
-
const v1=r[colKeys[1]]??null; if(vals[1]!==v1){patches.push({id,col:1,val:r[colKeys[1]]});vals[1]=v1}
|
|
898
|
-
} else if (nCols === 3) {
|
|
899
|
-
const v0=r[colKeys[0]]??null; if(vals[0]!==v0){patches.push({id,col:0,val:r[colKeys[0]]});vals[0]=v0}
|
|
900
|
-
const v1=r[colKeys[1]]??null; if(vals[1]!==v1){patches.push({id,col:1,val:r[colKeys[1]]});vals[1]=v1}
|
|
901
|
-
const v2=r[colKeys[2]]??null; if(vals[2]!==v2){patches.push({id,col:2,val:r[colKeys[2]]});vals[2]=v2}
|
|
902
|
-
} else if (nCols === 4) {
|
|
957
|
+
const nCols4 = nCols === 4
|
|
958
|
+
if (nCols4) {
|
|
903
959
|
const v0=r[colKeys[0]]??null; if(vals[0]!==v0){patches.push({id,col:0,val:r[colKeys[0]]});vals[0]=v0}
|
|
904
960
|
const v1=r[colKeys[1]]??null; if(vals[1]!==v1){patches.push({id,col:1,val:r[colKeys[1]]});vals[1]=v1}
|
|
905
961
|
const v2=r[colKeys[2]]??null; if(vals[2]!==v2){patches.push({id,col:2,val:r[colKeys[2]]});vals[2]=v2}
|
|
@@ -911,7 +967,7 @@ function _diffSync(rows, colKeys, rowCache) {
|
|
|
911
967
|
}
|
|
912
968
|
}
|
|
913
969
|
}
|
|
914
|
-
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)
|
|
915
971
|
return { patches, inserts, deletes }
|
|
916
972
|
}
|
|
917
973
|
|
|
@@ -952,6 +1008,16 @@ async function _renderIncremental(items, renderFn, chunkSize = 200) {
|
|
|
952
1008
|
}
|
|
953
1009
|
|
|
954
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
|
+
|
|
955
1021
|
function loadSSRData() {
|
|
956
1022
|
const ssr = window.__SSR_DATA__
|
|
957
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,
|