feat(nbstore): improve nbstore (#9512)

This commit is contained in:
EYHN
2025-01-06 09:38:03 +00:00
parent a2563d2180
commit 46c8c4a408
103 changed files with 3337 additions and 3423 deletions
@@ -0,0 +1,41 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`parseUniversalId > should parse universal id > @peer(@name);@type(userspace);@id(@id); 1`] = `
{
"id": "@id",
"peer": "@name",
"type": "userspace",
}
`;
exports[`parseUniversalId > should parse universal id > @peer(@peer(name);@type(userspace);@id(@id); 1`] = `
{
"id": "@id",
"peer": "@peer(name",
"type": "userspace",
}
`;
exports[`parseUniversalId > should parse universal id > @peer(123);@type(userspace);@id(456); 1`] = `
{
"id": "456",
"peer": "123",
"type": "userspace",
}
`;
exports[`parseUniversalId > should parse universal id > @peer(123);@type(workspace);@id(456); 1`] = `
{
"id": "456",
"peer": "123",
"type": "workspace",
}
`;
exports[`parseUniversalId > should parse universal id > @peer(https://app.affine.pro);@type(userspace);@id(hello:world); 1`] = `
{
"id": "hello:world",
"peer": "https://app.affine.pro",
"type": "userspace",
}
`;
@@ -0,0 +1,36 @@
import { describe, expect, it } from 'vitest';
import { parseUniversalId, universalId } from '../universal-id';
describe('parseUniversalId', () => {
it('should generate universal id', () => {
expect(universalId({ peer: '123', type: 'workspace', id: '456' })).toEqual(
'@peer(123);@type(workspace);@id(456);'
);
});
it('should parse universal id', () => {
const testcases = [
'@peer(123);@type(userspace);@id(456);',
'@peer(123);@type(workspace);@id(456);',
'@peer(https://app.affine.pro);@type(userspace);@id(hello:world);',
'@peer(@name);@type(userspace);@id(@id);',
'@peer(@peer(name);@type(userspace);@id(@id);',
];
testcases.forEach(id => {
expect(parseUniversalId(id)).toMatchSnapshot(id);
});
});
it('should throw invalid universal id', () => {
const testcases = [
'@peer(123);@type(anyspace);@id(456);', // invalid space type
'@peer(@peer(name););@type(userspace);@id(@id);', // invalid peer
];
testcases.forEach(id => {
expect(() => parseUniversalId(id)).toThrow();
});
});
});
@@ -0,0 +1,93 @@
export type SpaceType = 'workspace' | 'userspace';
export function universalId({
peer,
type,
id,
}: {
peer: string;
type: SpaceType;
id: string;
}) {
return `@peer(${peer});@type(${type});@id(${id});`;
}
export function isValidSpaceType(type: string): type is SpaceType {
return type === 'workspace' || type === 'userspace';
}
export function isValidUniversalId(opts: Record<string, string>): boolean {
const requiredKeys = ['peer', 'type', 'id'] as const;
for (const key of requiredKeys) {
if (!opts[key]) {
return false;
}
}
return isValidSpaceType(opts.type);
}
export function parseUniversalId(id: string): {
peer: string;
type: SpaceType;
id: string;
} {
const result: Partial<{
peer: string;
type: SpaceType;
id: string;
}> = {};
let key = '';
let value = '';
let isInValue = false;
let i = -1;
while (++i < id.length) {
const ch = id[i];
const nextCh = id[i + 1];
// when we are in value string, we only care about ch and next char to be [')', ';'] to end the id part
if (isInValue) {
if (ch === ')' && nextCh === ';') {
// @ts-expect-error we know the key is valid
result[key] = value;
key = '';
value = '';
isInValue = false;
i++;
continue;
}
value += ch;
continue;
}
if (ch === '@') {
const keyEnd = id.indexOf('(', i);
// we find '@' but no '(' in lookahead or '(' is immediately after '@', invalid id
if (keyEnd === -1 || keyEnd === i + 1) {
break;
}
key = id.slice(i + 1, keyEnd);
i = keyEnd;
isInValue = true;
} else {
break;
}
}
if (!isValidUniversalId(result)) {
throw new Error(
`Invalid universal storage id: ${id}. It should be in format of @peer(\${peer});@type(\${type});@id(\${id});`
);
}
return result as {
peer: string;
type: SpaceType;
id: string;
};
}