Skip to content

Commit 23f11af

Browse files
authored
Merge pull request #129 from leblowl/memoize-performance
Improve performance of memoized validator functions.
2 parents 31daeec + 4e9691c commit 23f11af

File tree

4 files changed

+50
-19
lines changed

4 files changed

+50
-19
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"test:pw": "pnpm -F @localfirst/automerge-repo-todos test:pw",
3333
"test:pw:log": "pnpm -F @localfirst/automerge-repo-todos test:pw:log",
3434
"test:pw:ui": "pnpm -F @localfirst/automerge-repo-todos test:pw:ui",
35+
"bench": "cross-env DEBUG=localfirst*,automerge* DEBUG_COLORS=1 vitest bench",
3536
"watch": "lerna watch -- lerna run build --scope=\\$LERNA_PACKAGE_NAME --include-dependents"
3637
},
3738
"devDependencies": {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, bench } from 'vitest'
2+
import * as Auth from '../index.js'
3+
4+
describe('auth', () => {
5+
bench('a new member joining', () => {
6+
const founderUsername = 'founder'
7+
const founderContext = {
8+
user: Auth.createUser(founderUsername, founderUsername),
9+
device: Auth.createDevice({ userId: founderUsername, deviceName: 'laptop' }),
10+
}
11+
const teamName = 'Test'
12+
const team = Auth.createTeam(teamName, founderContext)
13+
14+
// Add 100 test users
15+
16+
const usernames = Array.from({ length: 100 }, (_, i) => `user-${i}`)
17+
18+
for (const username of usernames) {
19+
const user = Auth.createUser(username, username)
20+
const device = Auth.createDevice({ userId: username, deviceName: 'dev/' + username })
21+
team.addForTesting(user, [], Auth.redactDevice(device))
22+
}
23+
24+
// Invite new user and have them join
25+
26+
const { seed } = team.inviteMember({ maxUses: 1000 })
27+
28+
const username = 'new-user'
29+
const user = Auth.createUser(username, username)
30+
const device = Auth.createDevice({ userId: username, deviceName: 'laptop' })
31+
const proofOfInvitation = Auth.generateProof(seed)
32+
33+
team.admitMember(proofOfInvitation, Auth.redactKeys(user.keys), user.userName)
34+
35+
const serializedGraph = team.save()
36+
const teamKeyring = team.teamKeyring()
37+
const team2 = new Auth.Team({ source: serializedGraph, context: { user, device }, teamKeyring })
38+
39+
team2.join(teamKeyring)
40+
})
41+
})

packages/crdx/src/validator/validate.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { type ValidationResult, type ValidatorSet } from './types.js'
1+
import { memoize } from '@localfirst/shared'
2+
import { type ValidationResult, type ValidatorSet } from './types.js'
23
import { fail, validators } from './validators.js'
34
import { VALID } from 'constants.js'
45
import { hashEncryptedLink } from 'graph/hashLink.js'
56
import { type Action, type Link, type Graph } from 'graph/types.js'
7+
import { hash } from '@localfirst/crypto'
68

79
/**
810
* Runs a hash graph through a series of validators to ensure that it is correctly formed, has
911
* not been tampered with, etc.
1012
*/
11-
export const validate = <A extends Action, C>(
13+
const _validate = <A extends Action, C>(
1214
/** The hash graph to validate. */
1315
graph: Graph<A, C>,
1416

@@ -79,6 +81,8 @@ export const validate = <A extends Action, C>(
7981
return VALID
8082
}
8183

84+
export const validate = memoize(_validate, graph => hash('memoize', graph))
85+
8286
// merges multiple validator sets into one object
8387
const merge = (validatorSets: ValidatorSet[]) =>
8488
validatorSets.reduce((result, vs) => Object.assign(result, vs), {})

packages/crdx/src/validator/validators.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { memoize } from '@localfirst/shared'
2-
import { hash } from '@localfirst/crypto'
3-
import { ROOT, VALID } from 'constants.js'
1+
import { ROOT, VALID } from 'constants.js'
42
import { getRoot } from 'graph/getRoot.js'
53
import { hashEncryptedLink } from 'graph/hashLink.js'
6-
import type { Graph, Link } from 'index.js'
74
import { ValidationError, type ValidatorSet } from './types.js'
85

9-
const _validators: ValidatorSet = {
6+
export const validators: ValidatorSet = {
107
/** Does this link's hash check out? */
118
validateHash(link, graph) {
129
const { hash } = link
@@ -82,15 +79,3 @@ export const fail = (msg: string, args?: any) => {
8279
error: new ValidationError(msg, args),
8380
}
8481
}
85-
86-
const memoizeFunctionMap = (source: ValidatorSet) => {
87-
const result = {} as ValidatorSet
88-
const memoizeResolver = (link: Link<any, any>, graph: Graph<any, any>) => {
89-
return `${hash('memoize', link)}:${hash('memoize', graph)}`
90-
}
91-
92-
for (const key in source) result[key] = memoize(source[key], memoizeResolver)
93-
return result
94-
}
95-
96-
export const validators = memoizeFunctionMap(_validators)

0 commit comments

Comments
 (0)