Use tanstackdb for record updates

This commit is contained in:
Hakan Shehu
2025-11-20 21:46:36 -08:00
parent 8ae7f007d8
commit 29bf4fbb27
20 changed files with 197 additions and 467 deletions

View File

@@ -46,11 +46,8 @@ import { NodeInteractionOpenedMutationHandler } from './nodes/node-interaction-o
import { NodeInteractionSeenMutationHandler } from './nodes/node-interaction-seen';
import { NodeReactionCreateMutationHandler } from './nodes/node-reaction-create';
import { NodeReactionDeleteMutationHandler } from './nodes/node-reaction-delete';
import { NodeUpdateMutationHandler } from './nodes/node-update';
import { PageUpdateMutationHandler } from './pages/page-update';
import { RecordAvatarUpdateMutationHandler } from './records/record-avatar-update';
import { RecordFieldValueDeleteMutationHandler } from './records/record-field-value-delete';
import { RecordFieldValueSetMutationHandler } from './records/record-field-value-set';
import { RecordNameUpdateMutationHandler } from './records/record-name-update';
import { ServerCreateMutationHandler } from './servers/server-create';
import { ServerDeleteMutationHandler } from './servers/server-delete';
import { ServerSyncMutationHandler } from './servers/server-sync';
@@ -78,6 +75,7 @@ export const buildMutationHandlerMap = (
'view.create': new ViewCreateMutationHandler(app),
'node.delete': new NodeDeleteMutationHandler(app),
'node.create': new NodeCreateMutationHandler(app),
'node.update': new NodeUpdateMutationHandler(app),
'chat.create': new ChatCreateMutationHandler(app),
'database.create': new DatabaseCreateMutationHandler(app),
'database.name.field.update': new DatabaseNameFieldUpdateMutationHandler(
@@ -94,10 +92,6 @@ export const buildMutationHandlerMap = (
'node.interaction.seen': new NodeInteractionSeenMutationHandler(app),
'node.reaction.create': new NodeReactionCreateMutationHandler(app),
'node.reaction.delete': new NodeReactionDeleteMutationHandler(app),
'record.avatar.update': new RecordAvatarUpdateMutationHandler(app),
'record.name.update': new RecordNameUpdateMutationHandler(app),
'record.field.value.delete': new RecordFieldValueDeleteMutationHandler(app),
'record.field.value.set': new RecordFieldValueSetMutationHandler(app),
'select.option.create': new SelectOptionCreateMutationHandler(app),
'select.option.delete': new SelectOptionDeleteMutationHandler(app),
'select.option.update': new SelectOptionUpdateMutationHandler(app),

View File

@@ -0,0 +1,24 @@
import { WorkspaceMutationHandlerBase } from '@colanode/client/handlers/mutations/workspace-mutation-handler-base';
import { MutationHandler } from '@colanode/client/lib/types';
import {
NodeUpdateMutationInput,
NodeUpdateMutationOutput,
} from '@colanode/client/mutations/nodes/node-update';
export class NodeUpdateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<NodeUpdateMutationInput>
{
async handleMutation(
input: NodeUpdateMutationInput
): Promise<NodeUpdateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
await workspace.nodes.updateNode(input.nodeId, () => {
return input.attributes;
});
return {
success: true,
};
}
}

View File

@@ -1,38 +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 {
RecordAvatarUpdateMutationInput,
RecordAvatarUpdateMutationOutput,
} from '@colanode/client/mutations/records/record-avatar-update';
import { RecordAttributes } from '@colanode/core';
export class RecordAvatarUpdateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<RecordAvatarUpdateMutationInput>
{
async handleMutation(
input: RecordAvatarUpdateMutationInput
): Promise<RecordAvatarUpdateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,
(attributes) => {
attributes.avatar = input.avatar;
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.RecordUpdateForbidden,
"You don't have permission to update this record."
);
}
return {
success: true,
};
}
}

View File

@@ -1,45 +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 {
RecordFieldValueDeleteMutationInput,
RecordFieldValueDeleteMutationOutput,
} from '@colanode/client/mutations/records/record-field-value-delete';
import { RecordAttributes } from '@colanode/core';
export class RecordFieldValueDeleteMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<RecordFieldValueDeleteMutationInput>
{
async handleMutation(
input: RecordFieldValueDeleteMutationInput
): Promise<RecordFieldValueDeleteMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,
(attributes) => {
delete attributes.fields[input.fieldId];
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.RecordUpdateForbidden,
"You don't have permission to delete this field value."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.RecordUpdateFailed,
'Something went wrong while deleting the field value. Please try again later.'
);
}
return {
success: true,
};
}
}

View File

@@ -1,45 +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 {
RecordFieldValueSetMutationInput,
RecordFieldValueSetMutationOutput,
} from '@colanode/client/mutations/records/record-field-value-set';
import { RecordAttributes } from '@colanode/core';
export class RecordFieldValueSetMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<RecordFieldValueSetMutationInput>
{
async handleMutation(
input: RecordFieldValueSetMutationInput
): Promise<RecordFieldValueSetMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,
(attributes) => {
attributes.fields[input.fieldId] = input.value;
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.RecordUpdateForbidden,
"You don't have permission to set this field value."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.RecordUpdateFailed,
'Something went wrong while setting the field value.'
);
}
return {
success: true,
};
}
}

View File

@@ -1,45 +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 {
RecordNameUpdateMutationInput,
RecordNameUpdateMutationOutput,
} from '@colanode/client/mutations/records/record-name-update';
import { RecordAttributes } from '@colanode/core';
export class RecordNameUpdateMutationHandler
extends WorkspaceMutationHandlerBase
implements MutationHandler<RecordNameUpdateMutationInput>
{
async handleMutation(
input: RecordNameUpdateMutationInput
): Promise<RecordNameUpdateMutationOutput> {
const workspace = this.getWorkspace(input.userId);
const result = await workspace.nodes.updateNode<RecordAttributes>(
input.recordId,
(attributes) => {
attributes.name = input.name;
return attributes;
}
);
if (result === 'unauthorized') {
throw new MutationError(
MutationErrorCode.RecordUpdateForbidden,
"You don't have permission to update this record."
);
}
if (result !== 'success') {
throw new MutationError(
MutationErrorCode.RecordUpdateFailed,
'Something went wrong while updating the record name. Please try again later.'
);
}
return {
success: true,
};
}
}

View File

@@ -37,10 +37,6 @@ export * from './nodes/node-interaction-seen';
export * from './nodes/node-reaction-create';
export * from './nodes/node-reaction-delete';
export * from './pages/page-update';
export * from './records/record-avatar-update';
export * from './records/record-field-value-delete';
export * from './records/record-field-value-set';
export * from './records/record-name-update';
export * from './servers/server-create';
export * from './servers/server-delete';
export * from './spaces/space-update';
@@ -58,6 +54,7 @@ export * from './servers/server-sync';
export * from './apps/tab-delete';
export * from './nodes/node-delete';
export * from './nodes/node-create';
export * from './nodes/node-update';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface MutationMap {}

View File

@@ -0,0 +1,21 @@
import { NodeAttributes } from '@colanode/core';
export type NodeUpdateMutationInput = {
type: 'node.update';
userId: string;
nodeId: string;
attributes: NodeAttributes;
};
export type NodeUpdateMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'node.update': {
input: NodeUpdateMutationInput;
output: NodeUpdateMutationOutput;
};
}
}

View File

@@ -1,19 +0,0 @@
export type RecordAvatarUpdateMutationInput = {
type: 'record.avatar.update';
userId: string;
recordId: string;
avatar: string;
};
export type RecordAvatarUpdateMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'record.avatar.update': {
input: RecordAvatarUpdateMutationInput;
output: RecordAvatarUpdateMutationOutput;
};
}
}

View File

@@ -1,19 +0,0 @@
export type RecordFieldValueDeleteMutationInput = {
type: 'record.field.value.delete';
userId: string;
recordId: string;
fieldId: string;
};
export type RecordFieldValueDeleteMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'record.field.value.delete': {
input: RecordFieldValueDeleteMutationInput;
output: RecordFieldValueDeleteMutationOutput;
};
}
}

View File

@@ -1,22 +0,0 @@
import { FieldValue } from '@colanode/core';
export type RecordFieldValueSetMutationInput = {
type: 'record.field.value.set';
userId: string;
recordId: string;
fieldId: string;
value: FieldValue;
};
export type RecordFieldValueSetMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'record.field.value.set': {
input: RecordFieldValueSetMutationInput;
output: RecordFieldValueSetMutationOutput;
};
}
}

View File

@@ -1,19 +0,0 @@
export type RecordNameUpdateMutationInput = {
type: 'record.name.update';
userId: string;
recordId: string;
name: string;
};
export type RecordNameUpdateMutationOutput = {
success: boolean;
};
declare module '@colanode/client/mutations' {
interface MutationMap {
'record.name.update': {
input: RecordNameUpdateMutationInput;
output: RecordNameUpdateMutationOutput;
};
}
}

View File

@@ -1,5 +1,5 @@
import { createCollection } from '@tanstack/react-db';
// import { cloneDeep } from 'lodash-es';
import { cloneDeep } from 'lodash-es';
import { mapNodeAttributes } from '@colanode/client/lib';
import { LocalNode } from '@colanode/client/types';
@@ -73,17 +73,18 @@ export const createNodesCollection = (userId: string) => {
});
}
},
// onUpdate: async ({ transaction }) => {
// for (const mutation of transaction.mutations) {
// const attributes = cloneDeep(mutation.modified.attributes);
// await window.colanode.executeMutation({
// type: 'node.update',
// userId,
// nodeId: mutation.key,
// attributes,
// });
// }
// },
onUpdate: async ({ transaction }) => {
for (const mutation of transaction.mutations) {
const node = cloneDeep(mutation.modified);
const attributes = mapNodeAttributes(node);
await window.colanode.executeMutation({
type: 'node.update',
userId,
nodeId: mutation.key,
attributes,
});
}
},
onDelete: async ({ transaction }) => {
for (const mutation of transaction.mutations) {
await window.colanode.executeMutation({

View File

@@ -1,6 +1,5 @@
import { eq, useLiveQuery as useLiveQueryTanstack } from '@tanstack/react-db';
import { CircleAlert, CircleDashed } from 'lucide-react';
import { toast } from 'sonner';
import {
CollaboratorFieldAttributes,
@@ -81,17 +80,16 @@ export const BoardViewColumnsCollaborator = ({
),
canDrag: (record) => record.canEdit,
onDragEnd: async (record, value) => {
const nodes = collections.workspace(workspace.userId).nodes;
if (!value) {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
} else {
if (value.type !== 'string_array') {
return;
@@ -114,17 +112,13 @@ export const BoardViewColumnsCollaborator = ({
};
}
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value: newValue,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = newValue;
});
}
},
}}
@@ -150,29 +144,24 @@ export const BoardViewColumnsCollaborator = ({
),
canDrag: () => true,
onDragEnd: async (record, value) => {
const nodes = collections.workspace(workspace.userId).nodes;
if (!value) {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
} else {
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = value;
});
}
},
}}

View File

@@ -1,5 +1,4 @@
import { CircleDashed } from 'lucide-react';
import { toast } from 'sonner';
import {
DatabaseViewFilterAttributes,
@@ -7,6 +6,7 @@ import {
MultiSelectFieldAttributes,
SelectOptionAttributes,
} from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { BoardViewColumn } from '@colanode/ui/components/databases/boards/board-view-column';
import { SelectOptionBadge } from '@colanode/ui/components/databases/fields/select-option-badge';
import { BoardViewContext } from '@colanode/ui/contexts/board-view';
@@ -90,17 +90,16 @@ export const BoardViewColumnsMultiSelect = ({
),
canDrag: (record) => record.canEdit,
onDragEnd: async (record, value) => {
const nodes = collections.workspace(workspace.userId).nodes;
if (!value) {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
} else {
if (value.type !== 'string_array') {
return;
@@ -122,17 +121,13 @@ export const BoardViewColumnsMultiSelect = ({
};
}
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value: newValue,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = newValue;
});
}
},
}}
@@ -159,29 +154,24 @@ export const BoardViewColumnsMultiSelect = ({
dragOverClass: noValueDraggingClass,
canDrag: () => true,
onDragEnd: async (record, value) => {
const nodes = collections.workspace(workspace.userId).nodes;
if (!value) {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
} else {
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = value;
});
}
},
}}

View File

@@ -1,11 +1,11 @@
import { CircleDashed } from 'lucide-react';
import { toast } from 'sonner';
import {
DatabaseViewFilterAttributes,
SelectFieldAttributes,
SelectOptionAttributes,
} from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { BoardViewColumn } from '@colanode/ui/components/databases/boards/board-view-column';
import { SelectOptionBadge } from '@colanode/ui/components/databases/fields/select-option-badge';
import { BoardViewContext } from '@colanode/ui/contexts/board-view';
@@ -89,29 +89,24 @@ export const BoardViewColumnsSelect = ({
),
canDrag: (record) => record.canEdit,
onDragEnd: async (record, value) => {
const nodes = collections.workspace(workspace.userId).nodes;
if (!value) {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
} else {
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = value;
});
}
},
}}
@@ -138,29 +133,24 @@ export const BoardViewColumnsSelect = ({
dragOverClass: noValueDraggingClass,
canDrag: () => true,
onDragEnd: async (record, value) => {
const nodes = collections.workspace(workspace.userId).nodes;
if (!value) {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
} else {
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = value;
});
}
},
}}

View File

@@ -1,13 +1,11 @@
import isHotkey from 'is-hotkey';
import { SquareArrowOutUpRight } from 'lucide-react';
import React, { Fragment } from 'react';
import { toast } from 'sonner';
import { RecordNode } from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { Link } from '@colanode/ui/components/ui/link';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
interface NameEditorProps {
initialValue: string;
@@ -61,27 +59,21 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
const workspace = useWorkspace();
const [isEditing, setIsEditing] = React.useState(false);
const { mutate, isPending } = useMutation();
const canEdit = true;
const hasName = record.name && record.name.length > 0;
const handleSave = (newName: string) => {
if (newName === record.name) return;
mutate({
input: {
type: 'record.name.update',
name: newName,
recordId: record.id,
userId: workspace.userId,
},
onSuccess() {
setIsEditing(false);
},
onError(error) {
toast.error(error.message);
},
const nodes = collections.workspace(workspace.userId).nodes;
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.name = newName;
});
setIsEditing(false);
};
return (
@@ -112,11 +104,6 @@ export const TableViewNameCell = ({ record }: TableViewNameCellProps) => {
>
<SquareArrowOutUpRight className="mr-1 size-4" /> <p>Open</p>
</Link>
{isPending && (
<span className="absolute right-2 text-muted-foreground">
<Spinner size="small" />
</span>
)}
</Fragment>
)}
</div>

View File

@@ -1,18 +1,14 @@
import { toast } from 'sonner';
import { collections } from '@colanode/ui/collections';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { AvatarPopover } from '@colanode/ui/components/avatars/avatar-popover';
import { Button } from '@colanode/ui/components/ui/button';
import { useRecord } from '@colanode/ui/contexts/record';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
export const RecordAvatar = () => {
const workspace = useWorkspace();
const record = useRecord();
const { mutate, isPending } = useMutation();
if (!record.canEdit) {
return (
<Button type="button" variant="outline" size="icon">
@@ -29,19 +25,14 @@ export const RecordAvatar = () => {
return (
<AvatarPopover
onPick={(avatar) => {
if (isPending) return;
if (avatar === record.avatar) return;
mutate({
input: {
type: 'record.avatar.update',
recordId: record.id,
avatar,
userId: workspace.userId,
},
onError(error) {
toast.error(error.message);
},
const nodes = collections.workspace(workspace.userId).nodes;
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.avatar = avatar;
});
}}
>

View File

@@ -1,15 +1,13 @@
import { useEffect, useRef } from 'react';
import { toast } from 'sonner';
import { collections } from '@colanode/ui/collections';
import { SmartTextInput } from '@colanode/ui/components/ui/smart-text-input';
import { useRecord } from '@colanode/ui/contexts/record';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
export const RecordName = () => {
const workspace = useWorkspace();
const record = useRecord();
const { mutate, isPending } = useMutation();
const inputRef = useRef<HTMLInputElement>(null);
@@ -29,24 +27,17 @@ export const RecordName = () => {
readOnly={!record.canEdit}
ref={inputRef}
onChange={(value) => {
if (isPending) {
return;
}
if (value === record.name) {
return;
}
mutate({
input: {
type: 'record.name.update',
recordId: record.id,
name: value,
userId: workspace.userId,
},
onError(error) {
toast.error(error.message);
},
const nodes = collections.workspace(workspace.userId).nodes;
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.name = value;
});
}}
className="font-heading border-b border-none pl-1 text-4xl font-bold shadow-none focus-visible:ring-0"

View File

@@ -2,6 +2,7 @@ import { toast } from 'sonner';
import { LocalRecordNode } from '@colanode/client/types';
import { NodeRole, hasNodeRole } from '@colanode/core';
import { collections } from '@colanode/ui/collections';
import { RecordContext } from '@colanode/ui/contexts/record';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
@@ -34,29 +35,35 @@ export const RecordProvider = ({
localRevision: record.localRevision,
canEdit,
updateFieldValue: async (field, value) => {
const result = await window.colanode.executeMutation({
type: 'record.field.value.set',
recordId: record.id,
fieldId: field.id,
value,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
const nodes = collections.workspace(workspace.userId).nodes;
if (!nodes.has(record.id)) {
toast.error('Record not found');
return;
}
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
draft.fields[field.id] = value;
});
},
removeFieldValue: async (field) => {
const result = await window.colanode.executeMutation({
type: 'record.field.value.delete',
recordId: record.id,
fieldId: field.id,
userId: workspace.userId,
});
if (!result.success) {
toast.error(result.error.message);
const nodes = collections.workspace(workspace.userId).nodes;
if (!nodes.has(record.id)) {
toast.error('Record not found');
return;
}
nodes.update(record.id, (draft) => {
if (draft.type !== 'record') {
return;
}
const { [field.id]: _removed, ...rest } = draft.fields;
draft.fields = rest;
});
},
getBooleanValue: (field) => {
const fieldValue = record.fields[field.id];