Use tanstackdb for some field mutations

This commit is contained in:
Hakan Shehu
2025-11-20 22:55:32 -08:00
parent e06bd40e72
commit 3033d12a24
19 changed files with 123 additions and 430 deletions

View File

@@ -1,47 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
import {
DatabaseNameFieldUpdateMutationInput,
DatabaseNameFieldUpdateMutationOutput,
} from '@colanode/client/mutations/databases/database-name-field-update';
import { DatabaseAttributes } from '@colanode/core';
export class DatabaseNameFieldUpdateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<DatabaseNameFieldUpdateMutationInput>
{
async handleMutation(
input: DatabaseNameFieldUpdateMutationInput
): Promise<DatabaseNameFieldUpdateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,
(attributes) => {
attributes.nameField = {
name: input.name,
};
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.DatabaseUpdateForbidden,
"You don't have permission to update this database."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.DatabaseUpdateFailed,
'Something went wrong while updating the database.'
);
}
return {
success: true,
};
}
}

View File

@@ -1,94 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import { fetchNode } from '@colanode/client/lib/utils';
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
import {
FieldCreateMutationInput,
FieldCreateMutationOutput,
} from '@colanode/client/mutations/databases/field-create';
import {
compareString,
DatabaseAttributes,
FieldAttributes,
FieldType,
generateId,
generateFractionalIndex,
IdType,
} from '@colanode/core';
export class FieldCreateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<FieldCreateMutationInput>
{
async handleMutation(
input: FieldCreateMutationInput
): Promise<FieldCreateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
if (input.fieldType === 'relation') {
if (!input.relationDatabaseId) {
throw new MutationError(
MutationErrorCode.RelationDatabaseNotFound,
'Relation database not found.'
);
}
const relationDatabase = await fetchNode(
workspace.database,
input.relationDatabaseId
);
if (!relationDatabase || relationDatabase.type !== 'database') {
throw new MutationError(
MutationErrorCode.RelationDatabaseNotFound,
'Relation database not found.'
);
}
}
const fieldId = generateId(IdType.Field);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,
(attributes) => {
const maxIndex = Object.values(attributes.fields)
.map((field) => field.index)
.sort((a, b) => -compareString(a, b))[0];
const index = generateFractionalIndex(maxIndex, null);
const newField: FieldAttributes = {
id: fieldId,
type: input.fieldType as FieldType,
name: input.name,
index,
};
if (newField.type === 'relation') {
newField.databaseId = input.relationDatabaseId;
}
attributes.fields[fieldId] = newField;
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.FieldCreateForbidden,
"You don't have permission to create a field in this database."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.FieldCreateFailed,
'Something went wrong while creating the field.'
);
}
return {
id: fieldId,
};
}
}

View File

@@ -1,46 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
import {
FieldDeleteMutationInput,
FieldDeleteMutationOutput,
} from '@colanode/client/mutations/databases/field-delete';
import { DatabaseAttributes } from '@colanode/core';
export class FieldDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<FieldDeleteMutationInput>
{
async handleMutation(
input: FieldDeleteMutationInput
): Promise<FieldDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,
(attributes) => {
delete attributes.fields[input.fieldId];
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.FieldDeleteForbidden,
"You don't have permission to delete this field."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.FieldDeleteFailed,
'Something went wrong while deleting the field.'
);
}
return {
id: input.fieldId,
};
}
}

View File

@@ -1,53 +0,0 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
import {
FieldNameUpdateMutationInput,
FieldNameUpdateMutationOutput,
} from '@colanode/client/mutations/databases/field-name-update';
import { DatabaseAttributes } from '@colanode/core';
export class FieldNameUpdateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<FieldNameUpdateMutationInput>
{
async handleMutation(
input: FieldNameUpdateMutationInput
): Promise<FieldNameUpdateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<DatabaseAttributes>(
input.databaseId,
(attributes) => {
const field = attributes.fields[input.fieldId];
if (!field) {
throw new MutationError(
MutationErrorCode.FieldNotFound,
'The field you are trying to update does not exist.'
);
}
field.name = input.name;
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.FieldUpdateForbidden,
"You don't have permission to update this field."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.FieldUpdateFailed,
'Something went wrong while updating the field.'
);
}
return {
id: input.fieldId,
};
}
}

View File

@@ -17,10 +17,6 @@ import { EmailVerifyMutationHandler } from './auth/email-verify';
import { GoogleLoginMutationHandler } from './auth/google-login'; import { GoogleLoginMutationHandler } from './auth/google-login';
import { AvatarUploadMutationHandler } from './avatars/avatar-upload'; import { AvatarUploadMutationHandler } from './avatars/avatar-upload';
import { ChatCreateMutationHandler } from './chats/chat-create'; import { ChatCreateMutationHandler } from './chats/chat-create';
import { DatabaseNameFieldUpdateMutationHandler } from './databases/database-name-field-update';
import { FieldCreateMutationHandler } from './databases/field-create';
import { FieldDeleteMutationHandler } from './databases/field-delete';
import { FieldNameUpdateMutationHandler } from './databases/field-name-update';
import { SelectOptionCreateMutationHandler } from './databases/select-option-create'; import { SelectOptionCreateMutationHandler } from './databases/select-option-create';
import { SelectOptionDeleteMutationHandler } from './databases/select-option-delete'; import { SelectOptionDeleteMutationHandler } from './databases/select-option-delete';
import { SelectOptionUpdateMutationHandler } from './databases/select-option-update'; import { SelectOptionUpdateMutationHandler } from './databases/select-option-update';
@@ -67,12 +63,6 @@ export const buildMutationHandlerMap = (
'node.create': new NodeCreateMutationHandler(app), 'node.create': new NodeCreateMutationHandler(app),
'node.update': new NodeUpdateMutationHandler(app), 'node.update': new NodeUpdateMutationHandler(app),
'chat.create': new ChatCreateMutationHandler(app), 'chat.create': new ChatCreateMutationHandler(app),
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
app
),
'field.create': new FieldCreateMutationHandler(app),
'field.delete': new FieldDeleteMutationHandler(app),
'field.name.update': new FieldNameUpdateMutationHandler(app),
'message.create': new MessageCreateMutationHandler(app), 'message.create': new MessageCreateMutationHandler(app),
'node.collaborator.create': new NodeCollaboratorCreateMutationHandler(app), 'node.collaborator.create': new NodeCollaboratorCreateMutationHandler(app),
'node.collaborator.delete': new NodeCollaboratorDeleteMutationHandler(app), 'node.collaborator.delete': new NodeCollaboratorDeleteMutationHandler(app),

View File

@@ -1,19 +0,0 @@
export type DatabaseNameFieldUpdateMutationInput = {
type: 'database.name.field.update';
userId: string;
databaseId: string;
name: string;
};
export type DatabaseNameFieldUpdateMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'database.name.field.update': {
input: DatabaseNameFieldUpdateMutationInput;
output: DatabaseNameFieldUpdateMutationOutput;
};
}
}

View File

@@ -1,23 +0,0 @@
import { FieldType } from '@colanode/core';
export type FieldCreateMutationInput = {
type: 'field.create';
userId: string;
databaseId: string;
name: string;
fieldType: FieldType;
relationDatabaseId?: string | null;
};
export type FieldCreateMutationOutput = {
id: string;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'field.create': {
input: FieldCreateMutationInput;
output: FieldCreateMutationOutput;
};
}
}

View File

@@ -1,19 +0,0 @@
export type FieldDeleteMutationInput = {
type: 'field.delete';
userId: string;
databaseId: string;
fieldId: string;
};
export type FieldDeleteMutationOutput = {
id: string;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'field.delete': {
input: FieldDeleteMutationInput;
output: FieldDeleteMutationOutput;
};
}
}

View File

@@ -1,20 +0,0 @@
export type FieldNameUpdateMutationInput = {
type: 'field.name.update';
userId: string;
databaseId: string;
fieldId: string;
name: string;
};
export type FieldNameUpdateMutationOutput = {
id: string;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'field.name.update': {
input: FieldNameUpdateMutationInput;
output: FieldNameUpdateMutationOutput;
};
}
}

View File

@@ -10,14 +10,10 @@ export * from './apps/metadata-delete';
export * from './apps/metadata-update'; export * from './apps/metadata-update';
export * from './avatars/avatar-upload'; export * from './avatars/avatar-upload';
export * from './chats/chat-create'; export * from './chats/chat-create';
export * from './databases/field-create';
export * from './databases/field-delete';
export * from './databases/field-name-update';
export * from './databases/select-option-create'; export * from './databases/select-option-create';
export * from './databases/select-option-delete'; export * from './databases/select-option-delete';
export * from './databases/select-option-update'; export * from './databases/select-option-update';
export * from './databases/view-update'; export * from './databases/view-update';
export * from './databases/database-name-field-update';
export * from './documents/document-update'; export * from './documents/document-update';
export * from './files/file-create'; export * from './files/file-create';
export * from './files/file-download'; export * from './files/file-download';

View File

@@ -49,7 +49,7 @@ export const DatabaseCreateDialog = ({
name: values.name, name: values.name,
parentId: spaceId, parentId: spaceId,
fields: { fields: {
[generateId(IdType.Field)]: { [fieldId]: {
id: fieldId, id: fieldId,
type: 'text', type: 'text',
index: generateFractionalIndex(null, null), index: generateFractionalIndex(null, null),

View File

@@ -28,64 +28,6 @@ export const Database = ({ database, role, children }: DatabaseProps) => {
canEdit, canEdit,
canCreateRecord, canCreateRecord,
rootId: database.rootId, rootId: database.rootId,
createField: async (type, name) => {
if (!canEdit) return;
const result = await window.colanode.executeMutation({
type: 'field.create',
databaseId: database.id,
name,
fieldType: type,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
}
},
renameField: async (id, name) => {
if (!canEdit) return;
const result = await window.colanode.executeMutation({
type: 'field.name.update',
databaseId: database.id,
fieldId: id,
name,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
}
},
updateNameField: async (name) => {
if (!canEdit) return;
const result = await window.colanode.executeMutation({
type: 'database.name.field.update',
databaseId: database.id,
name,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
}
},
deleteField: async (id) => {
if (!canEdit) return;
const result = await window.colanode.executeMutation({
type: 'field.delete',
databaseId: database.id,
fieldId: id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
}
},
createSelectOption: async (fieldId, name, color) => { createSelectOption: async (fieldId, name, color) => {
if (!canEdit) return; if (!canEdit) return;

View File

@@ -1,10 +1,20 @@
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react'; import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod/v4'; import { z } from 'zod/v4';
import { FieldType } from '@colanode/core'; import { MutationError, MutationErrorCode } from '@colanode/client/mutations';
import {
compareString,
FieldAttributes,
FieldType,
generateFractionalIndex,
generateId,
IdType,
} from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { DatabaseSelect } from '@colanode/ui/components/databases/database-select'; import { DatabaseSelect } from '@colanode/ui/components/databases/database-select';
import { FieldTypeSelect } from '@colanode/ui/components/databases/fields/field-type-select'; import { FieldTypeSelect } from '@colanode/ui/components/databases/fields/field-type-select';
import { Button } from '@colanode/ui/components/ui/button'; import { Button } from '@colanode/ui/components/ui/button';
@@ -25,7 +35,6 @@ import {
import { Spinner } from '@colanode/ui/components/ui/spinner'; import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useDatabase } from '@colanode/ui/contexts/database'; import { useDatabase } from '@colanode/ui/contexts/database';
import { useWorkspace } from '@colanode/ui/contexts/workspace'; import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(1, { message: 'Name is required' }), name: z.string().min(1, { message: 'Name is required' }),
@@ -50,6 +59,8 @@ const formSchema = z.object({
relationDatabaseId: z.string().optional().nullable(), relationDatabaseId: z.string().optional().nullable(),
}); });
type FieldCreateFormValues = z.infer<typeof formSchema>;
interface FieldCreatePopoverProps { interface FieldCreatePopoverProps {
button: React.ReactNode; button: React.ReactNode;
onSuccess?: (fieldId: string) => void; onSuccess?: (fieldId: string) => void;
@@ -65,9 +76,7 @@ export const FieldCreatePopover = ({
const workspace = useWorkspace(); const workspace = useWorkspace();
const database = useDatabase(); const database = useDatabase();
const { mutate, isPending } = useMutation(); const form = useForm<FieldCreateFormValues>({
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
name: '', name: '',
@@ -82,26 +91,71 @@ export const FieldCreatePopover = ({
form.reset(); form.reset();
}; };
const handleSubmit = (values: z.infer<typeof formSchema>) => { const { mutate, isPending } = useMutation({
mutate({ mutationFn: async (values: FieldCreateFormValues) => {
input: { const nodes = collections.workspace(workspace.userId).nodes;
type: 'field.create',
databaseId: database.id, if (values.type === 'relation') {
name: values.name, if (!values.relationDatabaseId) {
fieldType: values.type, throw new MutationError(
userId: workspace.userId, MutationErrorCode.RelationDatabaseNotFound,
relationDatabaseId: values.relationDatabaseId, 'Relation database not found.'
}, );
onSuccess: (output) => { }
setOpen(false);
form.reset(); const relationDatabase = nodes.get(values.relationDatabaseId);
onSuccess?.(output.id); if (!relationDatabase || relationDatabase.type !== 'database') {
}, throw new MutationError(
onError(error) { MutationErrorCode.RelationDatabaseNotFound,
toast.error(error.message); 'Relation database not found.'
}, );
}); }
}; }
if (!nodes.has(database.id)) {
return null;
}
const fieldId = generateId(IdType.Field);
nodes.update(database.id, (draft) => {
if (draft.type !== 'database') {
return;
}
const maxIndex = Object.values(draft.fields)
.map((field) => field.index)
.sort((a, b) => -compareString(a, b))[0];
const index = generateFractionalIndex(maxIndex, null);
const newField: FieldAttributes = {
id: fieldId,
type: values.type as FieldType,
name: values.name,
index,
};
if (newField.type === 'relation') {
newField.databaseId = values.relationDatabaseId;
}
draft.fields[fieldId] = newField;
});
return fieldId;
},
onSuccess: (fieldId) => {
form.reset();
setOpen(false);
if (fieldId) {
onSuccess?.(fieldId);
}
},
onError: (error) => {
toast.error(error.message as string);
},
});
if (!database.canEdit) { if (!database.canEdit) {
return null; return null;
@@ -114,7 +168,7 @@ export const FieldCreatePopover = ({
<Form {...form}> <Form {...form}>
<form <form
className="flex flex-col gap-2" className="flex flex-col gap-2"
onSubmit={form.handleSubmit(handleSubmit)} onSubmit={form.handleSubmit((values) => mutate(values))}
> >
<div className="grow space-y-4 py-2 pb-4"> <div className="grow space-y-4 py-2 pb-4">
<FormField <FormField

View File

@@ -1,3 +1,4 @@
import { collections } from '@colanode/ui/collections';
import { import {
AlertDialog, AlertDialog,
AlertDialogCancel, AlertDialogCancel,
@@ -9,6 +10,7 @@ import {
} from '@colanode/ui/components/ui/alert-dialog'; } from '@colanode/ui/components/ui/alert-dialog';
import { Button } from '@colanode/ui/components/ui/button'; import { Button } from '@colanode/ui/components/ui/button';
import { useDatabase } from '@colanode/ui/contexts/database'; import { useDatabase } from '@colanode/ui/contexts/database';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
interface FieldDeleteDialogProps { interface FieldDeleteDialogProps {
id: string; id: string;
@@ -21,6 +23,7 @@ export const FieldDeleteDialog = ({
open, open,
onOpenChange, onOpenChange,
}: FieldDeleteDialogProps) => { }: FieldDeleteDialogProps) => {
const workspace = useWorkspace();
const database = useDatabase(); const database = useDatabase();
return ( return (
@@ -40,7 +43,15 @@ export const FieldDeleteDialog = ({
<Button <Button
variant="destructive" variant="destructive"
onClick={async () => { onClick={async () => {
database.deleteField(id); const nodes = collections.workspace(workspace.userId).nodes;
nodes.update(database.id, (draft) => {
if (draft.type !== 'database') {
return;
}
const { [id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
onOpenChange(false); onOpenChange(false);
}} }}
> >

View File

@@ -1,12 +1,15 @@
import { FieldAttributes } from '@colanode/core'; import { FieldAttributes } from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input'; import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import { useDatabase } from '@colanode/ui/contexts/database'; import { useDatabase } from '@colanode/ui/contexts/database';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
interface FieldRenameInputProps { interface FieldRenameInputProps {
field: FieldAttributes; field: FieldAttributes;
} }
export const FieldRenameInput = ({ field }: FieldRenameInputProps) => { export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
const workspace = useWorkspace();
const database = useDatabase(); const database = useDatabase();
return ( return (
@@ -16,7 +19,20 @@ export const FieldRenameInput = ({ field }: FieldRenameInputProps) => {
readOnly={!database.canEdit} readOnly={!database.canEdit}
onChange={(newName) => { onChange={(newName) => {
if (newName === field.name) return; if (newName === field.name) return;
database.renameField(field.id, newName);
const nodes = collections.workspace(workspace.userId).nodes;
nodes.update(database.id, (draft) => {
if (draft.type !== 'database') {
return;
}
const fieldAttributes = draft.fields[field.id];
if (!fieldAttributes) {
return;
}
fieldAttributes.name = newName;
});
}} }}
/> />
</div> </div>

View File

@@ -4,6 +4,7 @@ import { Fragment, useRef, useState } from 'react';
import { useDrop } from 'react-dnd'; import { useDrop } from 'react-dnd';
import { SpecialId } from '@colanode/core'; import { SpecialId } from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
@@ -13,9 +14,11 @@ import { Separator } from '@colanode/ui/components/ui/separator';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input'; import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import { useDatabase } from '@colanode/ui/contexts/database'; import { useDatabase } from '@colanode/ui/contexts/database';
import { useDatabaseView } from '@colanode/ui/contexts/database-view'; import { useDatabaseView } from '@colanode/ui/contexts/database-view';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { cn } from '@colanode/ui/lib/utils'; import { cn } from '@colanode/ui/lib/utils';
export const TableViewNameHeader = () => { export const TableViewNameHeader = () => {
const workspace = useWorkspace();
const database = useDatabase(); const database = useDatabase();
const view = useDatabaseView(); const view = useDatabaseView();
@@ -90,7 +93,14 @@ export const TableViewNameHeader = () => {
readOnly={!database.canEdit} readOnly={!database.canEdit}
onChange={(newName) => { onChange={(newName) => {
if (newName === database.nameField?.name) return; if (newName === database.nameField?.name) return;
database.updateNameField(newName); const nodes = collections.workspace(workspace.userId).nodes;
nodes.update(database.id, (draft) => {
if (draft.type !== 'database') {
return;
}
draft.nameField = { name: newName };
});
}} }}
/> />
</div> </div>

View File

@@ -3,7 +3,6 @@ import { createContext, useContext } from 'react';
import { import {
DatabaseNameFieldAttributes, DatabaseNameFieldAttributes,
FieldAttributes, FieldAttributes,
FieldType,
NodeRole, NodeRole,
SelectOptionAttributes, SelectOptionAttributes,
} from '@colanode/core'; } from '@colanode/core';
@@ -17,10 +16,6 @@ interface DatabaseContext {
canCreateRecord: boolean; canCreateRecord: boolean;
role: NodeRole; role: NodeRole;
rootId: string; rootId: string;
createField: (type: FieldType, name: string) => void;
renameField: (id: string, name: string) => void;
updateNameField: (name: string) => void;
deleteField: (id: string) => void;
createSelectOption: (fieldId: string, name: string, color: string) => void; createSelectOption: (fieldId: string, name: string, color: string) => void;
updateSelectOption: ( updateSelectOption: (
fieldId: string, fieldId: string,

View File

@@ -32,7 +32,7 @@ export const DatabaseInlineCommand: EditorCommand = {
name: 'Untitled', name: 'Untitled',
parentId: documentId, parentId: documentId,
fields: { fields: {
[generateId(IdType.Field)]: { [fieldId]: {
id: fieldId, id: fieldId,
type: 'text', type: 'text',
index: generateFractionalIndex(null, null), index: generateFractionalIndex(null, null),

View File

@@ -32,7 +32,7 @@ export const DatabaseCommand: EditorCommand = {
name: 'Untitled', name: 'Untitled',
parentId: documentId, parentId: documentId,
fields: { fields: {
[generateId(IdType.Field)]: { [fieldId]: {
id: fieldId, id: fieldId,
type: 'text', type: 'text',
index: generateFractionalIndex(null, null), index: generateFractionalIndex(null, null),