mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-14 04:08:19 +00:00
Client model persistence (#3796)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
08a686e3d5
commit
f3db427f1d
@ -158,13 +158,22 @@ class ClientImpl implements AccountClient, BackupClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface TxPersistenceStore {
|
||||||
|
load: () => Promise<Tx[]>
|
||||||
|
store: (tx: Tx[]) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function createClient (
|
export async function createClient (
|
||||||
connect: (txHandler: TxHandler) => Promise<ClientConnection>,
|
connect: (txHandler: TxHandler) => Promise<ClientConnection>,
|
||||||
// If set will build model with only allowed plugins.
|
// If set will build model with only allowed plugins.
|
||||||
allowedPlugins?: Plugin[]
|
allowedPlugins?: Plugin[],
|
||||||
|
txPersistence?: TxPersistenceStore
|
||||||
): Promise<AccountClient> {
|
): Promise<AccountClient> {
|
||||||
let client: ClientImpl | null = null
|
let client: ClientImpl | null = null
|
||||||
|
|
||||||
@ -193,7 +202,7 @@ export async function createClient (
|
|||||||
|
|
||||||
const conn = await connect(txHandler)
|
const conn = await connect(txHandler)
|
||||||
|
|
||||||
lastTxTime = await loadModel(conn, lastTxTime, allowedPlugins, configs, hierarchy, model)
|
lastTxTime = await loadModel(conn, lastTxTime, allowedPlugins, configs, hierarchy, model, false, txPersistence)
|
||||||
|
|
||||||
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model || tx.modifiedOn > lastTxTime)
|
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model || tx.modifiedOn > lastTxTime)
|
||||||
|
|
||||||
@ -255,11 +264,18 @@ async function loadModel (
|
|||||||
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
|
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
|
||||||
hierarchy: Hierarchy,
|
hierarchy: Hierarchy,
|
||||||
model: ModelDb,
|
model: ModelDb,
|
||||||
reload = false
|
reload = false,
|
||||||
|
persistence?: TxPersistenceStore
|
||||||
): Promise<Timestamp> {
|
): Promise<Timestamp> {
|
||||||
const t = Date.now()
|
const t = Date.now()
|
||||||
|
|
||||||
let atxes = []
|
let ltxes: Tx[] = []
|
||||||
|
if (lastTxTime === 0 && persistence !== undefined) {
|
||||||
|
ltxes = await persistence.load()
|
||||||
|
lastTxTime = getLastTxTime(ltxes)
|
||||||
|
}
|
||||||
|
|
||||||
|
let atxes: Tx[] = []
|
||||||
try {
|
try {
|
||||||
atxes = await conn.loadModel(lastTxTime)
|
atxes = await conn.loadModel(lastTxTime)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -274,6 +290,12 @@ async function loadModel (
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (atxes.length < modelTransactionThreshold) {
|
||||||
|
atxes = ltxes.concat(atxes)
|
||||||
|
}
|
||||||
|
|
||||||
|
await persistence?.store(atxes)
|
||||||
|
|
||||||
let systemTx: Tx[] = []
|
let systemTx: Tx[] = []
|
||||||
const userTx: Tx[] = []
|
const userTx: Tx[] = []
|
||||||
console.log('find' + (lastTxTime >= 0 ? 'full model' : 'model diff'), atxes.length, Date.now() - t)
|
console.log('find' + (lastTxTime >= 0 ? 'full model' : 'model diff'), atxes.length, Date.now() - t)
|
||||||
@ -302,11 +324,7 @@ async function loadModel (
|
|||||||
|
|
||||||
const txes = systemTx.concat(userTx)
|
const txes = systemTx.concat(userTx)
|
||||||
|
|
||||||
for (const tx of txes) {
|
lastTxTime = getLastTxTime(txes)
|
||||||
if (tx.modifiedOn > lastTxTime) {
|
|
||||||
lastTxTime = tx.modifiedOn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const tx of txes) {
|
for (const tx of txes) {
|
||||||
try {
|
try {
|
||||||
@ -325,6 +343,16 @@ async function loadModel (
|
|||||||
return lastTxTime
|
return lastTxTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLastTxTime (txes: Tx[]): number {
|
||||||
|
let lastTxTime = 0
|
||||||
|
for (const tx of txes) {
|
||||||
|
if (tx.modifiedOn > lastTxTime) {
|
||||||
|
lastTxTime = tx.modifiedOn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastTxTime
|
||||||
|
}
|
||||||
|
|
||||||
function fillConfiguration (systemTx: Tx[], configs: Map<Ref<PluginConfiguration>, PluginConfiguration>): void {
|
function fillConfiguration (systemTx: Tx[], configs: Map<Ref<PluginConfiguration>, PluginConfiguration>): void {
|
||||||
for (const t of systemTx) {
|
for (const t of systemTx) {
|
||||||
if (t._class === core.class.TxCreateDoc) {
|
if (t._class === core.class.TxCreateDoc) {
|
||||||
|
@ -60,7 +60,24 @@ export default async () => {
|
|||||||
|
|
||||||
return connect(url.href, upgradeHandler, onUpgrade, onUnauthorized, onConnect)
|
return connect(url.href, upgradeHandler, onUpgrade, onUnauthorized, onConnect)
|
||||||
},
|
},
|
||||||
filterModel ? [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])] : undefined
|
filterModel ? [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])] : undefined,
|
||||||
|
{
|
||||||
|
load: async () => {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
const dta = localStorage.getItem('stored_model_' + token) ?? null
|
||||||
|
if (dta === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return JSON.parse(dta)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
store: async (txes) => {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.setItem('stored_model_' + token, JSON.stringify(txes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
// Check if we had dev hook for client.
|
// Check if we had dev hook for client.
|
||||||
client = hookClient(client)
|
client = hookClient(client)
|
||||||
|
@ -30,6 +30,7 @@ import core, {
|
|||||||
WorkspaceId,
|
WorkspaceId,
|
||||||
_getOperator,
|
_getOperator,
|
||||||
setObjectValue,
|
setObjectValue,
|
||||||
|
toFindResult,
|
||||||
versionToString
|
versionToString
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { DbAdapter } from '../adapter'
|
import { DbAdapter } from '../adapter'
|
||||||
@ -371,7 +372,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
|||||||
.filter((it) => it[1] > 3)
|
.filter((it) => it[1] > 3)
|
||||||
.map((it) => it[0])
|
.map((it) => it[0])
|
||||||
|
|
||||||
const result = await this.metrics.with(
|
let result = await this.metrics.with(
|
||||||
'get-to-index',
|
'get-to-index',
|
||||||
{},
|
{},
|
||||||
async () =>
|
async () =>
|
||||||
@ -390,6 +391,31 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
const toRemove: DocIndexState[] = []
|
||||||
|
// Check and remove missing class documents.
|
||||||
|
result = toFindResult(
|
||||||
|
result.filter((doc) => {
|
||||||
|
const _class = this.model.findObject(doc.objectClass)
|
||||||
|
if (_class === undefined) {
|
||||||
|
// no _class present, remove doc
|
||||||
|
toRemove.push(doc)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}),
|
||||||
|
result.total
|
||||||
|
)
|
||||||
|
|
||||||
|
if (toRemove.length > 0) {
|
||||||
|
try {
|
||||||
|
await this.storage.clean(
|
||||||
|
DOMAIN_DOC_INDEX_STATE,
|
||||||
|
toRemove.map((it) => it._id)
|
||||||
|
)
|
||||||
|
} catch (err: any) {
|
||||||
|
// QuotaExceededError, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
|
Loading…
Reference in New Issue
Block a user