Skip to content

feat: smart has-many removal, has-one remove(), dispatcher consistency#11

Merged
matej21 merged 5 commits intomainfrom
feat/has-many-removal-and-dispatcher
Apr 5, 2026
Merged

feat: smart has-many removal, has-one remove(), dispatcher consistency#11
matej21 merged 5 commits intomainfrom
feat/has-many-removal-and-dispatcher

Conversation

@matej21
Copy link
Copy Markdown
Member

@matej21 matej21 commented Apr 4, 2026

Summary

  • Smart remove() on HasMany: auto-detects disconnect vs delete based on schema metadata (relationKind + nullable). manyHasMany → disconnect, oneHasMany + nullable FK → disconnect, oneHasMany + non-nullable FK → delete.
  • Explicit delete() on HasMany: new method for force-deleting a related entity regardless of relation kind.
  • remove() on HasOne: consistent with HasMany — auto-detects disconnect vs delete from FK nullability.
  • Route HasMany through ActionDispatcher: all HasMany handle mutations (connect/disconnect/delete/add/remove/move) now go through dispatcher.dispatch(), enabling events, interceptors, and middleware. Previously these bypassed the dispatcher entirely, making onItemConnected/interceptItemConnecting/etc. dead code.
  • Schema enrichment: relationKind and nullable propagated through schema types, generator, and SchemaRegistry.
  • Deduplicate SchemaNames: removed duplicate type from bindx-react, imports from @contember/bindx.

Key changes

File Change
bindx-client/src/schema/types.ts Add HasManyRelationKind, nullable to relation defs
bindx/src/handles/HasManyListHandle.ts Smart remove(), explicit delete(), route all mutations through dispatcher
bindx/src/handles/HasOneHandle.ts Add remove() with nullable-based auto-detection
bindx/src/core/actions.ts Add CONNECT_TO_LIST action, alias to list actions, action creators
bindx/src/core/ActionDispatcher.ts Handle CONNECT_TO_LIST, pass alias in list handlers
bindx/src/events/eventFactory.ts Wire list actions to HasMany events
bindx/src/core/actionClassification.ts Register CONNECT_TO_LIST for undo/redo

Test plan

  • bun run typecheck passes
  • 619 unit tests pass (bun test tests/unit/)
  • Smart removal: manyHasMany → disconnect, oneHasMany → delete, nullable → disconnect, fallback → disconnect
  • disconnect()/delete() on created entities cancel the add (not plan nonsensical mutation)
  • Reconnect sequences: connect() after disconnect()/delete() cancels the removal
  • Events fire: connect()hasMany:connected, disconnect()hasMany:disconnected
  • Interceptors can cancel connect() and disconnect()

🤖 Generated with Claude Code

matej21 and others added 5 commits April 4, 2026 12:05
…remove() strategy

- Add `relationKind` ('oneHasMany' | 'manyHasMany') and `nullable` to HasManyRelationDef
- HasManyListHandle.remove() auto-detects removal type from schema:
  manyHasMany → disconnect, oneHasMany + nullable → disconnect,
  oneHasMany + non-nullable → delete
- Add explicit delete() method on HasManyListHandle/HasManyRef
- Fix disconnect()/delete() on created entities to cancel the add
  instead of planning a nonsensical removal for a temp entity
- Thread removalType through RemoveFromListAction → store chain
- Deduplicate SchemaNames type (import from @contember/bindx in react)
- Propagate relationKind/nullable from generator and SchemaRegistry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover all removal type combinations:
- remove() auto-detection: manyHasMany/oneHasMany/nullable/fallback
- disconnect()/delete() on created entities (cancel, not plan removal)
- reconnect sequences: connect() after disconnect()/delete()
- mixed removal types on same relation
- ordered IDs exclusion for both disconnect and delete
- REMOVE_FROM_LIST action with delete removalType

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tection

Consistent with HasMany.remove() — auto-detects disconnect vs delete:
- nullable FK → disconnect (sets FK to null)
- non-nullable FK → delete (related entity can't exist without parent)
- unknown → disconnect (safe fallback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously all HasMany handle mutations (connect, disconnect, delete,
add, remove, move) bypassed the ActionDispatcher and went directly to
SnapshotStore. This meant events/interceptors never fired, making
onItemConnected, interceptItemConnecting, etc. dead code.

- Add CONNECT_TO_LIST action for has-many connect operations
- Add alias parameter to all list actions (ADD_TO_LIST, REMOVE_FROM_LIST,
  MOVE_IN_LIST) so aliased relations work correctly through dispatcher
- Wire list actions to HasMany events in eventFactory (connecting/connected,
  disconnecting/disconnected)
- Route all HasManyListHandle mutation methods through dispatcher.dispatch()
- Add action creators: connectToList, addToList, removeFromList, moveInList

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…atcher

- connect() fires hasMany:connected listener
- disconnect() fires hasMany:disconnected listener
- add() fires hasMany:connected listener
- remove() fires hasMany:disconnected listener
- interceptor can cancel connect() and disconnect()
- CONNECT_TO_LIST action dispatches to store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@matej21 matej21 merged commit cc75313 into main Apr 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant