service.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /** React-free Client Workspace service and command facade. */
  2. import { Service, type Context } from '@deepseek-ai/cordis'
  3. import type { SessionId } from '@deepseek-ai/dsh-session/types'
  4. import type { RemoteFailure } from '@deepseek-ai/dsh-typert-protocol'
  5. import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types'
  6. import type { WorkspaceView } from '../types.ts'
  7. import type { ClientWorkspaceModel, WorkspaceSnapshot } from './model.ts'
  8. /** Structured create failure for callers that distinguish Host business errors. */
  9. export class WorkspaceCreateError extends Error {
  10. override readonly name = 'WorkspaceCreateError'
  11. /** @param rpcError - Host business or folded carrier failure. */
  12. constructor(readonly rpcError: RemoteFailure) {
  13. super(`workspace create failed: ${rpcError.code}: ${rpcError.message}`)
  14. }
  15. }
  16. /** Bare observable source for the Workspace Controller snapshot. */
  17. export interface WorkspaceSource {
  18. /** Read the identity-stable current snapshot. */
  19. getSnapshot(): WorkspaceSnapshot
  20. /**
  21. * Subscribe to snapshot changes.
  22. * @param listener - invalidation callback.
  23. * @returns unsubscribe function.
  24. */
  25. subscribe(listener: () => void): () => void
  26. }
  27. /** Workspace Controller's Client service face. */
  28. export interface IWorkspaces {
  29. /** Host-authoritative Workspace rows, order, archive set, and follow lifecycle. */
  30. readonly list: WorkspaceSource
  31. /**
  32. * Register an existing path as a Workspace.
  33. * @param input - Host create payload.
  34. * @returns the created or idempotently resolved Workspace.
  35. */
  36. create(input: { path: string }): Promise<WorkspaceView>
  37. /**
  38. * Rename a Workspace.
  39. * @param workspaceId - target Workspace.
  40. * @param title - new display title.
  41. * @returns the renamed Workspace.
  42. */
  43. rename(workspaceId: WorkspaceId, title: string): Promise<WorkspaceView>
  44. /**
  45. * Delete a Workspace registration without deleting Sessions or files.
  46. * @param workspaceId - target Workspace.
  47. */
  48. delete(workspaceId: WorkspaceId): Promise<void>
  49. /**
  50. * Move a Workspace within the Host registry order.
  51. * @param workspaceId - Workspace to move.
  52. * @param beforeWorkspaceId - anchor Workspace; omitted appends.
  53. */
  54. insertBefore(workspaceId: WorkspaceId, beforeWorkspaceId?: WorkspaceId): Promise<void>
  55. /**
  56. * Archive a Session from Workspace grouping surfaces.
  57. * @param sessionId - Session to archive.
  58. */
  59. archiveSession(sessionId: SessionId): Promise<void>
  60. /**
  61. * Unarchive a Session from the archived Session list.
  62. * @param sessionId - Session to unarchive.
  63. */
  64. unarchiveSession(sessionId: SessionId): Promise<void>
  65. /**
  66. * Move a Session within one Workspace account.
  67. * @param workspaceId - owning Workspace.
  68. * @param sessionId - Session to move.
  69. * @param beforeSessionId - anchor Session; omitted appends.
  70. * @returns the changed Workspace.
  71. */
  72. insertSessionBefore(
  73. workspaceId: WorkspaceId,
  74. sessionId: SessionId,
  75. beforeSessionId?: SessionId,
  76. ): Promise<WorkspaceView>
  77. }
  78. /** Owns the bare Workspace snapshot and Workspace-only commands. */
  79. export class WorkspaceController extends Service implements IWorkspaces {
  80. readonly list: WorkspaceSource
  81. /**
  82. * @param ctx - Client root Context.
  83. * @param model - Remote-backed Workspace state model.
  84. */
  85. constructor(ctx: Context, private readonly model: ClientWorkspaceModel) {
  86. super(ctx, 'workspaces')
  87. this.list = model
  88. }
  89. async create(input: { path: string }): Promise<WorkspaceView> {
  90. const result = await this.model.create(input)
  91. if (!result.ok) throw new WorkspaceCreateError(result.error)
  92. return result.value.workspace
  93. }
  94. async rename(workspaceId: WorkspaceId, title: string): Promise<WorkspaceView> {
  95. const result = await this.model.rename(workspaceId, title)
  96. if (!result.ok) throw commandError('rename', result.error)
  97. return result.value.workspace
  98. }
  99. async delete(workspaceId: WorkspaceId): Promise<void> {
  100. const result = await this.model.delete(workspaceId)
  101. if (!result.ok) throw commandError('delete', result.error)
  102. }
  103. async insertBefore(workspaceId: WorkspaceId, beforeWorkspaceId?: WorkspaceId): Promise<void> {
  104. const result = await this.model.insertBefore(workspaceId, beforeWorkspaceId)
  105. if (!result.ok) throw commandError('reorder', result.error)
  106. }
  107. async archiveSession(sessionId: SessionId): Promise<void> {
  108. const result = await this.model.archiveSession(sessionId)
  109. if (!result.ok) throw commandError('session archive', result.error)
  110. }
  111. async unarchiveSession(sessionId: SessionId): Promise<void> {
  112. const result = await this.model.unarchiveSession(sessionId)
  113. if (!result.ok) throw commandError('session unarchive', result.error)
  114. }
  115. async insertSessionBefore(
  116. workspaceId: WorkspaceId,
  117. sessionId: SessionId,
  118. beforeSessionId?: SessionId,
  119. ): Promise<WorkspaceView> {
  120. const result = await this.model.insertSessionBefore(workspaceId, sessionId, beforeSessionId)
  121. if (!result.ok) throw commandError('move', result.error)
  122. return result.value.workspace
  123. }
  124. }
  125. function commandError(operation: string, failure: RemoteFailure): Error {
  126. return new Error(`workspace ${operation} failed: ${failure.code}: ${failure.message}`)
  127. }