refactor(core): move infra modules to core (#9207)
This commit is contained in:
33
packages/frontend/core/src/modules/db/entities/db.ts
Normal file
33
packages/frontend/core/src/modules/db/entities/db.ts
Normal 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]>;
|
||||
};
|
||||
39
packages/frontend/core/src/modules/db/entities/table.ts
Normal file
39
packages/frontend/core/src/modules/db/entities/table.ts
Normal 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;
|
||||
}
|
||||
18
packages/frontend/core/src/modules/db/index.ts
Normal file
18
packages/frontend/core/src/modules/db/index.ts
Normal 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]);
|
||||
}
|
||||
7
packages/frontend/core/src/modules/db/schema/index.ts
Normal file
7
packages/frontend/core/src/modules/db/schema/index.ts
Normal 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';
|
||||
52
packages/frontend/core/src/modules/db/schema/schema.ts
Normal file
52
packages/frontend/core/src/modules/db/schema/schema.ts
Normal 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;
|
||||
128
packages/frontend/core/src/modules/db/services/db.ts
Normal file
128
packages/frontend/core/src/modules/db/services/db.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user