refactor(core): move infra modules to core (#9207)

This commit is contained in:
EYHN
2024-12-23 04:53:59 +00:00
parent 2ea79d25ad
commit 129f94ee78
337 changed files with 908 additions and 1367 deletions

View File

@@ -0,0 +1,33 @@
import {
type DBSchemaBuilder,
Entity,
type TableMap,
} from '@toeverything/infra';
import { WorkspaceDBTable } from './table';
export class WorkspaceDB<Schema extends DBSchemaBuilder> extends Entity<{
db: TableMap<Schema>;
schema: Schema;
storageDocId: (tableName: string) => string;
}> {
readonly db = this.props.db;
constructor() {
super();
Object.entries(this.props.schema).forEach(([tableName]) => {
const table = this.framework.createEntity(WorkspaceDBTable, {
table: this.db[tableName],
storageDocId: this.props.storageDocId(tableName),
});
Object.defineProperty(this, tableName, {
get: () => table,
});
});
}
}
export type WorkspaceDBWithTables<Schema extends DBSchemaBuilder> =
WorkspaceDB<Schema> & {
[K in keyof Schema]: WorkspaceDBTable<Schema[K]>;
};

View File

@@ -0,0 +1,39 @@
import type {
Table as OrmTable,
TableSchemaBuilder,
} from '@toeverything/infra';
import { Entity } from '@toeverything/infra';
import type { WorkspaceService } from '../../workspace';
export class WorkspaceDBTable<
Schema extends TableSchemaBuilder,
> extends Entity<{
table: OrmTable<Schema>;
storageDocId: string;
}> {
readonly table = this.props.table;
constructor(private readonly workspaceService: WorkspaceService) {
super();
}
isSyncing$ = this.workspaceService.workspace.engine.doc
.docState$(this.props.storageDocId)
.map(docState => docState.syncing);
isLoading$ = this.workspaceService.workspace.engine.doc
.docState$(this.props.storageDocId)
.map(docState => docState.loading);
create = this.table.create.bind(this.table) as typeof this.table.create;
update = this.table.update.bind(this.table) as typeof this.table.update;
get = this.table.get.bind(this.table) as typeof this.table.get;
// eslint-disable-next-line rxjs/finnish
get$ = this.table.get$.bind(this.table) as typeof this.table.get$;
find = this.table.find.bind(this.table) as typeof this.table.find;
// eslint-disable-next-line rxjs/finnish
find$ = this.table.find$.bind(this.table) as typeof this.table.find$;
keys = this.table.keys.bind(this.table) as typeof this.table.keys;
delete = this.table.delete.bind(this.table) as typeof this.table.delete;
}

View File

@@ -0,0 +1,18 @@
import type { Framework } from '@toeverything/infra';
import { WorkspaceScope, WorkspaceService } from '../workspace';
import { WorkspaceDB } from './entities/db';
import { WorkspaceDBTable } from './entities/table';
import { WorkspaceDBService } from './services/db';
export type { DocCustomPropertyInfo, DocProperties } from './schema';
export { WorkspaceDBService } from './services/db';
export { transformWorkspaceDBLocalToCloud } from './services/db';
export function configureWorkspaceDBModule(framework: Framework) {
framework
.scope(WorkspaceScope)
.service(WorkspaceDBService, [WorkspaceService])
.entity(WorkspaceDB)
.entity(WorkspaceDBTable, [WorkspaceService]);
}

View File

@@ -0,0 +1,7 @@
export type { DocCustomPropertyInfo, DocProperties } from './schema';
export {
AFFiNE_WORKSPACE_DB_SCHEMA,
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
type AFFiNEWorkspaceDbSchema,
type AFFiNEWorkspaceUserdataDbSchema,
} from './schema';

View File

@@ -0,0 +1,52 @@
import {
type DBSchemaBuilder,
f,
type ORMEntity,
t,
} from '@toeverything/infra';
import { nanoid } from 'nanoid';
export const AFFiNE_WORKSPACE_DB_SCHEMA = {
folders: {
id: f.string().primaryKey().optional().default(nanoid),
parentId: f.string().optional(),
data: f.string(),
type: f.string(),
index: f.string(),
},
docProperties: t.document({
// { [`custom:{customPropertyId}`]: any }
id: f.string().primaryKey(),
primaryMode: f.string().optional(),
edgelessColorTheme: f.string().optional(),
journal: f.string().optional(),
pageWidth: f.string().optional(),
}),
docCustomPropertyInfo: {
id: f.string().primaryKey().optional().default(nanoid),
name: f.string().optional(),
type: f.string(),
show: f.string().optional(),
index: f.string().optional(),
icon: f.string().optional(),
additionalData: f.json().optional(),
isDeleted: f.boolean().optional(),
// we will keep deleted properties in the database, for override legacy data
},
} as const satisfies DBSchemaBuilder;
export type AFFiNEWorkspaceDbSchema = typeof AFFiNE_WORKSPACE_DB_SCHEMA;
export type DocProperties = ORMEntity<AFFiNEWorkspaceDbSchema['docProperties']>;
export type DocCustomPropertyInfo = ORMEntity<
AFFiNEWorkspaceDbSchema['docCustomPropertyInfo']
>;
export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = {
favorite: {
key: f.string().primaryKey(),
index: f.string(),
},
} as const satisfies DBSchemaBuilder;
export type AFFiNEWorkspaceUserdataDbSchema =
typeof AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA;

View File

@@ -0,0 +1,128 @@
import {
createORMClient,
type DocStorage,
ObjectPool,
Service,
YjsDBAdapter,
} from '@toeverything/infra';
import { Doc as YDoc } from 'yjs';
import type { WorkspaceService } from '../../workspace';
import { WorkspaceDB, type WorkspaceDBWithTables } from '../entities/db';
import {
AFFiNE_WORKSPACE_DB_SCHEMA,
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
type AFFiNEWorkspaceDbSchema,
type AFFiNEWorkspaceUserdataDbSchema,
} from '../schema';
const WorkspaceDBClient = createORMClient(AFFiNE_WORKSPACE_DB_SCHEMA);
const WorkspaceUserdataDBClient = createORMClient(
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA
);
export class WorkspaceDBService extends Service {
db: WorkspaceDBWithTables<AFFiNEWorkspaceDbSchema>;
userdataDBPool = new ObjectPool<
string,
WorkspaceDB<AFFiNEWorkspaceUserdataDbSchema>
>({
onDangling() {
return false; // never release
},
});
constructor(private readonly workspaceService: WorkspaceService) {
super();
this.db = this.framework.createEntity(
WorkspaceDB<AFFiNEWorkspaceDbSchema>,
{
db: new WorkspaceDBClient(
new YjsDBAdapter(AFFiNE_WORKSPACE_DB_SCHEMA, {
getDoc: guid => {
const ydoc = new YDoc({
// guid format: db${workspaceId}${guid}
guid: `db$${this.workspaceService.workspace.id}$${guid}`,
});
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
this.workspaceService.workspace.engine.doc.setPriority(
ydoc.guid,
50
);
return ydoc;
},
})
),
schema: AFFiNE_WORKSPACE_DB_SCHEMA,
storageDocId: tableName =>
`db$${this.workspaceService.workspace.id}$${tableName}`,
}
) as WorkspaceDBWithTables<AFFiNEWorkspaceDbSchema>;
}
userdataDB(userId: (string & {}) | '__local__') {
// __local__ for local workspace
const userdataDb = this.userdataDBPool.get(userId);
if (userdataDb) {
return userdataDb.obj as WorkspaceDBWithTables<AFFiNEWorkspaceUserdataDbSchema>;
}
const newDB = this.framework.createEntity(
WorkspaceDB<AFFiNEWorkspaceUserdataDbSchema>,
{
db: new WorkspaceUserdataDBClient(
new YjsDBAdapter(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA, {
getDoc: guid => {
const ydoc = new YDoc({
// guid format: userdata${userId}${workspaceId}${guid}
guid: `userdata$${userId}$${this.workspaceService.workspace.id}$${guid}`,
});
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
this.workspaceService.workspace.engine.doc.setPriority(
ydoc.guid,
50
);
return ydoc;
},
})
),
schema: AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
storageDocId: tableName =>
`userdata$${userId}$${this.workspaceService.workspace.id}$${tableName}`,
}
);
this.userdataDBPool.put(userId, newDB);
return newDB as WorkspaceDBWithTables<AFFiNEWorkspaceUserdataDbSchema>;
}
static isDBDocId(docId: string) {
return docId.startsWith('db$') || docId.startsWith('userdata$');
}
}
export async function transformWorkspaceDBLocalToCloud(
localWorkspaceId: string,
cloudWorkspaceId: string,
localDocStorage: DocStorage,
cloudDocStorage: DocStorage,
accountId: string
) {
for (const tableName of Object.keys(AFFiNE_WORKSPACE_DB_SCHEMA)) {
const localDocName = `db$${localWorkspaceId}$${tableName}`;
const localDoc = await localDocStorage.doc.get(localDocName);
if (localDoc) {
const cloudDocName = `db$${cloudWorkspaceId}$${tableName}`;
await cloudDocStorage.doc.set(cloudDocName, localDoc);
}
}
for (const tableName of Object.keys(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA)) {
const localDocName = `userdata$__local__$${localWorkspaceId}$${tableName}`;
const localDoc = await localDocStorage.doc.get(localDocName);
if (localDoc) {
const cloudDocName = `userdata$${accountId}$${cloudWorkspaceId}$${tableName}`;
await cloudDocStorage.doc.set(cloudDocName, localDoc);
}
}
}