perf: stop recomputing the full client list per client on bulk push
pushAllClients() already has every client profile from its own listClients() call, but looped through pushClient(key) -> getClient(key) -> listClients() again for each one - N clients meant N+1 full order-history recomputations instead of one. pushClient now accepts an optional already-known profile so the bulk path skips the redundant lookup; single-key callers (scheduleServerPush's debounce) are unaffected. compareClientSources() had the same shape of duplicate work: it called buildClients()/serverListClients() directly and then again inside mergeClientSources(). mergeClientSources() now accepts already-computed local/server arrays instead of always recomputing both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
60d00e9f56
commit
a08633f9d0
@ -118,19 +118,20 @@
|
||||
return {key,name:String(row?.name||identity?.name||data?.name||''),phone:String(row?.phone||identity?.phone||data?.phone||''),latestAddress,addresses:latestAddress?[latestAddress]:[],orderIds:[],orderCount:0,totalSpent:0,loyalty:Object.prototype.hasOwnProperty.call(data,'loyalty')?clone(data.loyalty):null,communication:Object.prototype.hasOwnProperty.call(data,'communication')?clone(data.communication):null,serverVersion:Number(row?.version||0)||null,serverUpdatedAt:String(row?.updated_at||''),serverSourceKeys:Array.isArray(row?.source_keys)?row.source_keys.map(String):[],serverSourceCount:Number(row?.source_count||0)||1,canonicalRowPresent:Boolean(row?.canonical_row_present)};
|
||||
}
|
||||
function serverListClients(){return currentServerRows().map(serverProfile).filter(Boolean)}
|
||||
function mergeClientSources(){
|
||||
const local=buildClients(),byKey=new Map(local.map(item=>[item.key,{...clone(item),dataSource:'local'}]));
|
||||
for(const server of serverListClients()){
|
||||
if(server.key.startsWith('o:')&&!byKey.has(server.key))continue;
|
||||
const current=byKey.get(server.key)||{key:server.key,name:'',phone:'',latestAddress:'',addresses:[],orderIds:[],orderCount:0,totalSpent:0,loyalty:null,communication:null};
|
||||
const addresses=[...new Set([...(current.addresses||[]),...(server.addresses||[]),server.latestAddress].map(v=>String(v||'').trim()).filter(Boolean))];
|
||||
byKey.set(server.key,{...current,name:server.name||current.name,phone:server.phone||current.phone,latestAddress:server.latestAddress||current.latestAddress,addresses,loyalty:server.loyalty??current.loyalty,communication:server.communication??current.communication,serverVersion:server.serverVersion,serverUpdatedAt:server.serverUpdatedAt,serverSourceKeys:server.serverSourceKeys,serverSourceCount:server.serverSourceCount,canonicalRowPresent:server.canonicalRowPresent,dataSource:'server+local'});
|
||||
function mergeClientSources(local,server){
|
||||
local=local||buildClients();server=server||serverListClients();
|
||||
const byKey=new Map(local.map(item=>[item.key,{...clone(item),dataSource:'local'}]));
|
||||
for(const row of server){
|
||||
if(row.key.startsWith('o:')&&!byKey.has(row.key))continue;
|
||||
const current=byKey.get(row.key)||{key:row.key,name:'',phone:'',latestAddress:'',addresses:[],orderIds:[],orderCount:0,totalSpent:0,loyalty:null,communication:null};
|
||||
const addresses=[...new Set([...(current.addresses||[]),...(row.addresses||[]),row.latestAddress].map(v=>String(v||'').trim()).filter(Boolean))];
|
||||
byKey.set(row.key,{...current,name:row.name||current.name,phone:row.phone||current.phone,latestAddress:row.latestAddress||current.latestAddress,addresses,loyalty:row.loyalty??current.loyalty,communication:row.communication??current.communication,serverVersion:row.serverVersion,serverUpdatedAt:row.serverUpdatedAt,serverSourceKeys:row.serverSourceKeys,serverSourceCount:row.serverSourceCount,canonicalRowPresent:row.canonicalRowPresent,dataSource:'server+local'});
|
||||
}
|
||||
return [...byKey.values()].sort((a,b)=>(b.orderCount-a.orderCount)||String(a.name||a.key).localeCompare(String(b.name||b.key),'ru'));
|
||||
}
|
||||
function listClients(options={}){const source=typeof options==='string'?options:String(options?.source||'auto');if(source==='local'||source==='legacy')return clone(buildClients());if(source==='server')return clone(serverListClients());return clone(mergeClientSources())}
|
||||
function compareClientSources(){
|
||||
const local=buildClients(),server=serverListClients(),merged=mergeClientSources(),serverKeys=new Set(server.map(x=>x.key)),localKeys=new Set(local.map(x=>x.key));
|
||||
const local=buildClients(),server=serverListClients(),merged=mergeClientSources(local,server),serverKeys=new Set(server.map(x=>x.key)),localKeys=new Set(local.map(x=>x.key));
|
||||
const metricMismatches=local.map(item=>{const m=merged.find(x=>x.key===item.key);return !m||m.orderCount!==item.orderCount||Number(m.totalSpent)!==Number(item.totalSpent)?item.key:null}).filter(Boolean);
|
||||
return {localCount:local.length,serverCount:server.length,mergedCount:merged.length,serverPreferred:server.length>0,missingOnServer:[...localKeys].filter(k=>!serverKeys.has(k)),serverOnly:[...serverKeys].filter(k=>!localKeys.has(k)),legacyServerFallbackRows:server.filter(x=>x.key.startsWith('o:')).length,orderMetricsEqual:metricMismatches.length===0,metricMismatches};
|
||||
}
|
||||
@ -171,8 +172,8 @@
|
||||
function setClientLoyalty(key,value,{pushServer=true}={}){writeLegacyMap(LEGACY_LOYALTY_KEY,String(key),value);adoptLegacy({pushServer:false,reason:'client.loyalty'});if(pushServer)scheduleServerPush([String(key)]);return clone(getClient(key)?.loyalty||null)}
|
||||
function setClientCommunication(key,value,{pushServer=true}={}){writeLegacyMap(LEGACY_COMM_KEY,String(key),value);adoptLegacy({pushServer:false,reason:'client.communication'});if(pushServer)scheduleServerPush([String(key)]);return clone(getClient(key)?.communication||null)}
|
||||
function serverPayload(profile){return {schemaVersion:1,key:profile.key,identity:{name:String(profile.name||''),phone:String(profile.phone||''),latestAddress:String(profile.latestAddress||'')},loyalty:profile.loyalty?clone(profile.loyalty):null,communication:profile.communication?clone(profile.communication):null,source:'data-layer-v1772',updatedAt:new Date().toISOString()}}
|
||||
async function pushClient(key){
|
||||
const profile=getClient(key);if(!profile)return {status:'missing',key};if(!isSignedIn())return {status:'local',key};if(isSupportReadOnly()||!canWrite('clients.edit'))return {status:'read-only',key};
|
||||
async function pushClient(key,knownProfile){
|
||||
const profile=knownProfile||getClient(key);if(!profile)return {status:'missing',key};if(!isSignedIn())return {status:'local',key};if(isSupportReadOnly()||!canWrite('clients.edit'))return {status:'read-only',key};
|
||||
const c=cloud()?.getClient?.(),ws=workspace();if(!c?.rpc||!ws?.id)return {status:'offline',key};
|
||||
const cached=readClientCache()[key]||{},expected=Number(cached.serverVersion||0)||null;
|
||||
const {data,error}=await c.rpc('sun_v17_save_client_v1772',{p_workspace:ws.id,p_client_key:key,p_profile:serverPayload(profile),p_expected_version:expected,p_client_id:cloud()?.getClientId?.()||'browser'});if(error)throw error;
|
||||
@ -181,7 +182,7 @@
|
||||
}
|
||||
const pendingServerKeys=new Set();let serverPushTimer=0;
|
||||
function scheduleServerPush(keys){for(const key of keys||[])if(key)pendingServerKeys.add(String(key));if(serverPushTimer)return;serverPushTimer=setTimeout(async()=>{serverPushTimer=0;const keys=[...pendingServerKeys];pendingServerKeys.clear();for(const key of keys){try{await pushClient(key)}catch(error){console.warn('[Caterium Clients] server save failed',key,error?.message||error)}}},250)}
|
||||
async function pushAllClients(){const result=[];for(const profile of listClients()){try{result.push(await pushClient(profile.key))}catch(error){result.push({status:'error',key:profile.key,error:String(error?.message||error)})}}return result}
|
||||
async function pushAllClients(){const result=[];for(const profile of listClients()){try{result.push(await pushClient(profile.key,profile))}catch(error){result.push({status:'error',key:profile.key,error:String(error?.message||error)})}}return result}
|
||||
|
||||
let bridgeSuppressed=false;
|
||||
function installLegacyClientBridge(){
|
||||
|
||||
Loading…
Reference in New Issue
Block a user