diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 5945f3a9..3d3b2fac 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -96,7 +96,6 @@ "clsx": "^2.1.1", "cmdk": "^1.0.4", "electron-squirrel-startup": "^1.0.1", - "fractional-indexing-jittered": "^0.9.1", "is-hotkey": "^0.2.0", "lowlight": "^3.1.0", "lucide-react": "^0.460.0", diff --git a/apps/desktop/src/main/data/workspace/migrations.ts b/apps/desktop/src/main/data/workspace/migrations.ts index 3781a85f..c2daa55c 100644 --- a/apps/desktop/src/main/data/workspace/migrations.ts +++ b/apps/desktop/src/main/data/workspace/migrations.ts @@ -19,9 +19,6 @@ const createNodesTable: Migration = { .onDelete('cascade') .notNull() ) - .addColumn('index', 'text', (col) => - col.generatedAlwaysAs(sql`json_extract(attributes, '$.index')`).stored() - ) .addColumn('attributes', 'text', (col) => col.notNull()) .addColumn('state', 'blob', (col) => col.notNull()) .addColumn('created_at', 'text', (col) => col.notNull()) diff --git a/apps/desktop/src/main/data/workspace/schema.ts b/apps/desktop/src/main/data/workspace/schema.ts index 6c1d9860..61dca0d4 100644 --- a/apps/desktop/src/main/data/workspace/schema.ts +++ b/apps/desktop/src/main/data/workspace/schema.ts @@ -4,7 +4,6 @@ interface NodeTable { id: ColumnType; parent_id: ColumnType; type: ColumnType; - index: ColumnType; attributes: ColumnType; state: ColumnType; created_at: ColumnType; diff --git a/apps/desktop/src/main/mutations/channel-create.ts b/apps/desktop/src/main/mutations/channel-create.ts index f98be0b8..38a5403c 100644 --- a/apps/desktop/src/main/mutations/channel-create.ts +++ b/apps/desktop/src/main/mutations/channel-create.ts @@ -1,6 +1,5 @@ import { databaseService } from '@/main/data/database-service'; import { generateId, IdType } from '@colanode/core'; -import { generateNodeIndex } from '@/shared/lib/nodes'; import { MutationHandler } from '@/main/types'; import { ChannelCreateMutationInput, @@ -29,23 +28,12 @@ export class ChannelCreateMutationHandler throw new Error('Space not found'); } - const maxIndexResult = await workspaceDatabase - .selectFrom('nodes') - .select(['index']) - .where('parent_id', '=', input.spaceId) - .orderBy('index', 'desc') - .limit(1) - .executeTakeFirst(); - - const maxIndex = maxIndexResult?.index ?? null; const id = generateId(IdType.Channel); - const attributes: ChannelAttributes = { type: 'channel', name: input.name, avatar: input.avatar, parentId: input.spaceId, - index: generateNodeIndex(maxIndex, null), collaborators: null, }; diff --git a/apps/desktop/src/main/mutations/database-create.ts b/apps/desktop/src/main/mutations/database-create.ts index 53e11f3a..320d7b13 100644 --- a/apps/desktop/src/main/mutations/database-create.ts +++ b/apps/desktop/src/main/mutations/database-create.ts @@ -1,11 +1,14 @@ -import { generateId, IdType } from '@colanode/core'; -import { generateNodeIndex } from '@/shared/lib/nodes'; +import { + generateId, + IdType, + DatabaseAttributes, + generateNodeIndex, +} from '@colanode/core'; import { MutationHandler } from '@/main/types'; import { DatabaseCreateMutationInput, DatabaseCreateMutationOutput, } from '@/shared/mutations/database-create'; -import { DatabaseAttributes } from '@colanode/core'; import { nodeService } from '@/main/services/node-service'; export class DatabaseCreateMutationHandler diff --git a/apps/desktop/src/main/mutations/field-create.ts b/apps/desktop/src/main/mutations/field-create.ts index 73434bf7..e2c8748b 100644 --- a/apps/desktop/src/main/mutations/field-create.ts +++ b/apps/desktop/src/main/mutations/field-create.ts @@ -1,5 +1,4 @@ -import { generateId, IdType } from '@colanode/core'; -import { generateNodeIndex } from '@/shared/lib/nodes'; +import { generateId, IdType, generateNodeIndex } from '@colanode/core'; import { compareString } from '@/shared/lib/utils'; import { MutationHandler } from '@/main/types'; import { diff --git a/apps/desktop/src/main/mutations/select-option-create.ts b/apps/desktop/src/main/mutations/select-option-create.ts index 05d04eec..9bc4cfc5 100644 --- a/apps/desktop/src/main/mutations/select-option-create.ts +++ b/apps/desktop/src/main/mutations/select-option-create.ts @@ -1,5 +1,4 @@ -import { generateId, IdType } from '@colanode/core'; -import { generateNodeIndex } from '@/shared/lib/nodes'; +import { generateId, IdType, generateNodeIndex } from '@colanode/core'; import { MutationHandler } from '@/main/types'; import { SelectOptionCreateMutationInput, diff --git a/apps/desktop/src/main/mutations/space-create.ts b/apps/desktop/src/main/mutations/space-create.ts index b30f0b37..35e9382b 100644 --- a/apps/desktop/src/main/mutations/space-create.ts +++ b/apps/desktop/src/main/mutations/space-create.ts @@ -1,5 +1,4 @@ import { generateId, IdType, NodeRoles } from '@colanode/core'; -import { generateNodeIndex } from '@/shared/lib/nodes'; import { MutationHandler } from '@/main/types'; import { SpaceCreateMutationInput, @@ -59,7 +58,6 @@ export class SpaceCreateMutationHandler type: 'channel', name: 'Discussions', parentId: spaceId, - index: generateNodeIndex(null, null), }; await nodeService.createNode(input.userId, [ diff --git a/apps/desktop/src/main/mutations/view-create.ts b/apps/desktop/src/main/mutations/view-create.ts index a3d89f83..61250af8 100644 --- a/apps/desktop/src/main/mutations/view-create.ts +++ b/apps/desktop/src/main/mutations/view-create.ts @@ -1,11 +1,10 @@ -import { generateId, IdType } from '@colanode/core'; +import { generateId, IdType, generateNodeIndex } from '@colanode/core'; import { MutationHandler } from '@/main/types'; import { ViewCreateMutationInput, ViewCreateMutationOutput, } from '@/shared/mutations/view-create'; import { compareString } from '@/shared/lib/utils'; -import { generateNodeIndex } from '@/shared/lib/nodes'; import { nodeService } from '@/main/services/node-service'; export class ViewCreateMutationHandler diff --git a/apps/desktop/src/main/utils.ts b/apps/desktop/src/main/utils.ts index 819a2f88..b6882495 100644 --- a/apps/desktop/src/main/utils.ts +++ b/apps/desktop/src/main/utils.ts @@ -87,7 +87,6 @@ export const mapNode = (row: SelectNode): Node => { return { id: row.id, type: row.type as any, - index: row.index, parentId: row.parent_id, attributes: JSON.parse(row.attributes), createdAt: row.created_at, diff --git a/apps/desktop/src/renderer/components/spaces/space-sidebar-item.tsx b/apps/desktop/src/renderer/components/spaces/space-sidebar-item.tsx index 8279cf9b..b3836bcb 100644 --- a/apps/desktop/src/renderer/components/spaces/space-sidebar-item.tsx +++ b/apps/desktop/src/renderer/components/spaces/space-sidebar-item.tsx @@ -40,6 +40,7 @@ import { ChevronRight, } from 'lucide-react'; import { useQuery } from '@/renderer/hooks/use-query'; +import { compareString } from '@/shared/lib/utils'; interface SettingsState { open: boolean; @@ -60,7 +61,7 @@ export const SpaceSidebarItem = ({ node }: SpaceSidebarItemProps) => { types: ['page', 'channel', 'database', 'folder'], }); - const children = data ?? []; + const children = (data ?? []).toSorted((a, b) => compareString(a.id, b.id)); const [openCreatePage, setOpenCreatePage] = React.useState(false); const [openCreateChannel, setOpenCreateChannel] = React.useState(false); diff --git a/apps/desktop/src/shared/lib/databases.ts b/apps/desktop/src/shared/lib/databases.ts index b222b33b..84b5fde7 100644 --- a/apps/desktop/src/shared/lib/databases.ts +++ b/apps/desktop/src/shared/lib/databases.ts @@ -9,9 +9,9 @@ import { MultiSelectFieldAttributes, SelectFieldAttributes, ViewType, + generateNodeIndex, } from '@colanode/core'; import { compareString, isStringArray } from '@/shared/lib/utils'; -import { generateNodeIndex } from '@/shared/lib/nodes'; export const getDefaultFieldWidth = (type: FieldType): number => { if (!type) return 0; diff --git a/apps/desktop/src/shared/lib/editor.ts b/apps/desktop/src/shared/lib/editor.ts index 885e85c6..187ab89c 100644 --- a/apps/desktop/src/shared/lib/editor.ts +++ b/apps/desktop/src/shared/lib/editor.ts @@ -1,5 +1,9 @@ -import { EditorNodeTypes, generateId, getIdTypeFromNode } from '@colanode/core'; -import { generateNodeIndex } from '@/shared/lib/nodes'; +import { + EditorNodeTypes, + generateId, + getIdTypeFromNode, + generateNodeIndex, +} from '@colanode/core'; import { compareString } from '@/shared/lib/utils'; import { JSONContent } from '@tiptap/core'; import { Block, BlockLeaf } from '@colanode/core'; diff --git a/apps/desktop/src/shared/lib/nodes.ts b/apps/desktop/src/shared/lib/nodes.ts index 9fab4a3b..e6dd9ea3 100644 --- a/apps/desktop/src/shared/lib/nodes.ts +++ b/apps/desktop/src/shared/lib/nodes.ts @@ -1,17 +1,6 @@ -import { generateKeyBetween } from 'fractional-indexing-jittered'; import { extractNodeCollaborators, Node, NodeTypes } from '@colanode/core'; import { NodeCollaborator } from '@/shared/types/nodes'; -export const generateNodeIndex = ( - previous?: string | null, - next?: string | null -) => { - const lower = previous === undefined ? null : previous; - const upper = next === undefined ? null : next; - - return generateKeyBetween(lower, upper); -}; - export const getDefaultNodeIcon = (type: string) => { switch (type) { case NodeTypes.Channel: diff --git a/apps/server/src/data/migrations.ts b/apps/server/src/data/migrations.ts index d63aeb62..d1da4a38 100644 --- a/apps/server/src/data/migrations.ts +++ b/apps/server/src/data/migrations.ts @@ -111,9 +111,6 @@ const createNodesTable: Migration = { .onDelete('cascade') .notNull() ) - .addColumn('index', 'varchar(30)', (col) => - col.generatedAlwaysAs(sql`(attributes->>'index')::VARCHAR(30)`).stored() - ) .addColumn('attributes', 'jsonb', (col) => col.notNull()) .addColumn('state', 'bytea', (col) => col.notNull()) .addColumn('created_at', 'timestamptz', (col) => col.notNull()) diff --git a/apps/server/src/data/schema.ts b/apps/server/src/data/schema.ts index 1273925b..63a4ef28 100644 --- a/apps/server/src/data/schema.ts +++ b/apps/server/src/data/schema.ts @@ -86,7 +86,6 @@ interface NodeTable { workspace_id: ColumnType; parent_id: ColumnType; type: ColumnType; - index: ColumnType; attributes: JSONColumnType; state: ColumnType; created_at: ColumnType; diff --git a/apps/server/src/lib/nodes.ts b/apps/server/src/lib/nodes.ts index a6d97636..9c0d6562 100644 --- a/apps/server/src/lib/nodes.ts +++ b/apps/server/src/lib/nodes.ts @@ -16,7 +16,6 @@ export const mapNodeOutput = (node: SelectNode): NodeOutput => { parentId: node.parent_id, workspaceId: node.workspace_id, type: node.type, - index: node.index, attributes: node.attributes, state: fromUint8Array(node.state), createdAt: node.created_at.toISOString(), @@ -34,7 +33,6 @@ export const mapNode = (node: SelectNode): Node => { id: node.id, parentId: node.parent_id, type: node.type as NodeType, - index: node.index, attributes: node.attributes, createdAt: node.created_at.toISOString(), createdBy: node.created_by, diff --git a/apps/server/src/lib/workspaces.ts b/apps/server/src/lib/workspaces.ts index d9f8bb66..671df188 100644 --- a/apps/server/src/lib/workspaces.ts +++ b/apps/server/src/lib/workspaces.ts @@ -209,7 +209,6 @@ const buildChannelNodeCreate = ( type: 'channel', name: 'Discussions', parentId: spaceId, - index: '0', }; const ydoc = new YDoc(id); diff --git a/apps/server/src/routes/workspaces.ts b/apps/server/src/routes/workspaces.ts index 82da85e0..024c87da 100644 --- a/apps/server/src/routes/workspaces.ts +++ b/apps/server/src/routes/workspaces.ts @@ -619,7 +619,6 @@ workspacesRouter.post( serverCreatedAt: new Date().toISOString(), versionId: userVersionId, workspaceId: workspace.id, - index: null, }; usersToCreate.push({ @@ -776,7 +775,6 @@ workspacesRouter.put( id: user.id, type: user.type, workspaceId: user.workspace_id, - index: null, parentId: workspace.id, attributes: userDoc.getAttributes(), state: userDoc.getEncodedState(), diff --git a/package-lock.json b/package-lock.json index 3920b37e..6874dc6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,6 @@ "clsx": "^2.1.1", "cmdk": "^1.0.4", "electron-squirrel-startup": "^1.0.1", - "fractional-indexing-jittered": "^0.9.1", "is-hotkey": "^0.2.0", "lowlight": "^3.1.0", "lucide-react": "^0.460.0", @@ -18397,6 +18396,7 @@ "name": "@colanode/core", "version": "1.0.0", "dependencies": { + "fractional-indexing-jittered": "^0.9.1", "ulid": "^2.3.0", "zod": "^3.23.8" }, diff --git a/packages/core/package.json b/packages/core/package.json index c55caa88..f5193562 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -25,6 +25,7 @@ "vitest": "^2.1.5" }, "dependencies": { + "fractional-indexing-jittered": "^0.9.1", "ulid": "^2.3.0", "zod": "^3.23.8" } diff --git a/packages/core/src/lib/nodes.ts b/packages/core/src/lib/nodes.ts index d472c006..1af0f560 100644 --- a/packages/core/src/lib/nodes.ts +++ b/packages/core/src/lib/nodes.ts @@ -1,4 +1,5 @@ import { Node, NodeAttributes, NodeRole } from '../index'; +import { generateKeyBetween } from 'fractional-indexing-jittered'; export const extractNodeCollaborators = ( attributes: NodeAttributes @@ -55,3 +56,13 @@ export const hasViewerAccess = (role: NodeRole | null): boolean => { role === 'viewer' ); }; + +export const generateNodeIndex = ( + previous?: string | null, + next?: string | null +) => { + const lower = previous === undefined ? null : previous; + const upper = next === undefined ? null : next; + + return generateKeyBetween(lower, upper); +}; diff --git a/packages/core/src/registry/channel.ts b/packages/core/src/registry/channel.ts index 462bd697..347364f3 100644 --- a/packages/core/src/registry/channel.ts +++ b/packages/core/src/registry/channel.ts @@ -7,7 +7,6 @@ export const channelAttributesSchema = z.object({ name: z.string(), avatar: z.string().nullable().optional(), parentId: z.string(), - index: z.string(), collaborators: z.record(z.string()).nullable().optional(), }); diff --git a/packages/core/src/registry/index.ts b/packages/core/src/registry/index.ts index 2a3ca19c..051a2d4c 100644 --- a/packages/core/src/registry/index.ts +++ b/packages/core/src/registry/index.ts @@ -14,7 +14,6 @@ import { WorkspaceAttributes, workspaceModel } from './workspace'; type NodeBase = { id: string; parentId: string; - index: string | null; createdAt: string; createdBy: string; updatedAt: string | null; diff --git a/packages/core/src/types/nodes.ts b/packages/core/src/types/nodes.ts index c7258233..a460ad90 100644 --- a/packages/core/src/types/nodes.ts +++ b/packages/core/src/types/nodes.ts @@ -5,7 +5,6 @@ export type NodeOutput = { workspaceId: string; parentId: string; type: string; - index: string | null; attributes: NodeAttributes; state: string; createdAt: string;