OpenFn / OpenFn/lightning

Consider using Y.Map instead of Y.Array for jobs/triggers/edges in Y.Doc

Open
#4,253 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Collab Editor feature enhancement
Dominant language
Elixir
Stars
296
Forks
86
Avg merge
1d 13h
Merged PRs (30d)
50

Description

Context

When multiple users apply an AI-generated workflow simultaneously, the Y.Array CRDT can create duplicate entries. This happens because Y.Array's push() operation is always additive - concurrent pushes from different users all get merged.

Current data structure:

ydoc.getArray('jobs')     // Y.Array<Y.Map>
ydoc.getArray('triggers') // Y.Array<Y.Map>
ydoc.getArray('edges')    // Y.Array<Y.Map>

Current apply logic (in YAMLStateToYDoc.applyToYDoc):

const jobsArray = ydoc.getArray('jobs');
jobsArray.delete(0, jobsArray.length);  // Clear
jobsArray.push(transformedJobs);        // Add new

When two users click Apply concurrently:

  1. Both delete operations target the same items
  2. Both push operations add the same items
  3. CRDT merge includes BOTH pushes → duplicate entries

Current Solution (Implemented)

We implemented server-coordinated locking via Phoenix Channel:

  • When a user clicks Apply, backend broadcasts workflow_applying to all clients
  • All clients disable their Apply buttons
  • When apply completes, backend broadcasts workflow_applied
  • All clients re-enable Apply buttons

This is 100% effective because the server is the single source of truth.

Files changed:

  • lib/lightning_web/channels/workflow_channel.ex - Added start_applying_workflow and done_applying_workflow handlers
  • assets/js/collaborative-editor/stores/createWorkflowStore.ts - Channel listeners and coordination methods
  • assets/js/collaborative-editor/components/AIAssistantPanelWrapper.tsx - Calls coordination methods

Proposed Future Improvement

Change the Y.Doc structure to use Y.Map keyed by ID:

ydoc.getMap('jobs')       // Y.Map<id, Y.Map>
ydoc.getMap('triggers')   // Y.Map<id, Y.Map>
ydoc.getMap('edges')      // Y.Map<id, Y.Map>

Why this would be better:

  • map.set(id, data) is idempotent - two users setting the same key = same result, no duplicates
  • More semantically correct (entities are keyed by ID)
  • Simpler apply logic without needing coordination

What would need to change:

  1. Frontend:

    • YAMLStateToYDoc.applyToYDoc - Use map.set(id, data) instead of array.push()
    • createWorkflowStore.ts - Update observers to read from maps
    • Convert map values to arrays for Immer state (React expects arrays)
  2. Backend:

    • lib/lightning/collaboration/workflow_serializer.ex - Change Yex.Doc.get_array to Yex.Doc.get_map
    • Update initialization and extraction logic

Priority

Low - The server-coordinated solution works perfectly. This is an architectural improvement for cleaner CRDT semantics, not a bug fix.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing YAMLStateToYDoc.applyToYDoc and the observers in assets/js/collaborative-editor/stores/createWorkflowStore.ts, then inspect lib/lightning/collaboration/workflow_serializer.ex. The migration is done when jobs, triggers, and edges use keyed maps consistently and frontend state still receives arrays without duplicate entries during concurrent applies.

Written by the indexing model from the issue text.

Assessment

Tech stack
elixir, typescript
Domain
backend, distributed-systems, frontend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.