@plandesk/api 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/events.d.ts +5 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +2 -0
  4. package/dist/projection.d.ts +51 -0
  5. package/dist/projection.js +75 -0
  6. package/dist/services/index.d.ts +4 -0
  7. package/dist/services/index.js +6 -0
  8. package/dist/services/projects.js +5 -1
  9. package/dist/services/share.d.ts +41 -0
  10. package/dist/services/share.js +65 -0
  11. package/dist/services/sync.d.ts +74 -0
  12. package/dist/services/sync.js +297 -0
  13. package/package.json +5 -5
  14. package/web/_redirects +1 -0
  15. package/web/assets/index-DK5m0p5b.js +171 -0
  16. package/web/index.html +1 -1
  17. package/dist/auth.d.ts.map +0 -1
  18. package/dist/auth.js.map +0 -1
  19. package/dist/events.d.ts.map +0 -1
  20. package/dist/events.js.map +0 -1
  21. package/dist/index.d.ts.map +0 -1
  22. package/dist/index.js.map +0 -1
  23. package/dist/routes/agent-runs.d.ts.map +0 -1
  24. package/dist/routes/agent-runs.js.map +0 -1
  25. package/dist/routes/canvas.d.ts.map +0 -1
  26. package/dist/routes/canvas.js.map +0 -1
  27. package/dist/routes/comments.d.ts.map +0 -1
  28. package/dist/routes/comments.js.map +0 -1
  29. package/dist/routes/documents.d.ts.map +0 -1
  30. package/dist/routes/documents.js.map +0 -1
  31. package/dist/routes/events.d.ts.map +0 -1
  32. package/dist/routes/events.js.map +0 -1
  33. package/dist/routes/health.d.ts.map +0 -1
  34. package/dist/routes/health.js.map +0 -1
  35. package/dist/routes/projects.d.ts.map +0 -1
  36. package/dist/routes/projects.js.map +0 -1
  37. package/dist/routes/tasks.d.ts.map +0 -1
  38. package/dist/routes/tasks.js.map +0 -1
  39. package/dist/routes/tokens.d.ts.map +0 -1
  40. package/dist/routes/tokens.js.map +0 -1
  41. package/dist/serialize.d.ts.map +0 -1
  42. package/dist/serialize.js.map +0 -1
  43. package/dist/server.d.ts.map +0 -1
  44. package/dist/server.js.map +0 -1
  45. package/dist/services/agent-runs.d.ts.map +0 -1
  46. package/dist/services/agent-runs.js.map +0 -1
  47. package/dist/services/canvas.d.ts.map +0 -1
  48. package/dist/services/canvas.js.map +0 -1
  49. package/dist/services/comments.d.ts.map +0 -1
  50. package/dist/services/comments.js.map +0 -1
  51. package/dist/services/documents.d.ts.map +0 -1
  52. package/dist/services/documents.js.map +0 -1
  53. package/dist/services/index.d.ts.map +0 -1
  54. package/dist/services/index.js.map +0 -1
  55. package/dist/services/projects.d.ts.map +0 -1
  56. package/dist/services/projects.js.map +0 -1
  57. package/dist/services/tasks.d.ts.map +0 -1
  58. package/dist/services/tasks.js.map +0 -1
  59. package/dist/services/tokens.d.ts.map +0 -1
  60. package/dist/services/tokens.js.map +0 -1
  61. package/dist/static.d.ts.map +0 -1
  62. package/dist/static.js.map +0 -1
  63. package/dist/test-helpers.d.ts.map +0 -1
  64. package/dist/test-helpers.js.map +0 -1
  65. package/web/assets/index-C7JiW6wQ.js +0 -171
package/dist/events.d.ts CHANGED
@@ -39,7 +39,11 @@ export type AgentRunCompletedEvent = {
39
39
  runId: string;
40
40
  projectId: string;
41
41
  };
42
- export type PlankDeskEvent = TaskUpdatedEvent | CanvasUpdatedEvent | DocumentCreatedEvent | CommentCreatedEvent | CommentUpdatedEvent | AgentRunStartedEvent | AgentRunProgressEvent | AgentRunCompletedEvent;
42
+ export type SubmissionsPulledEvent = {
43
+ type: 'submissions_pulled';
44
+ projectId: string;
45
+ };
46
+ export type PlankDeskEvent = TaskUpdatedEvent | CanvasUpdatedEvent | DocumentCreatedEvent | CommentCreatedEvent | CommentUpdatedEvent | AgentRunStartedEvent | AgentRunProgressEvent | AgentRunCompletedEvent | SubmissionsPulledEvent;
43
47
  export type EventListener = (event: PlankDeskEvent) => void;
44
48
  export type EventBus = {
45
49
  subscribe(listener: EventListener): () => void;
package/dist/index.d.ts CHANGED
@@ -15,5 +15,7 @@ export { InvalidCommentError } from './services/comments.js';
15
15
  export { InvalidCanvasError } from './services/canvas.js';
16
16
  export { InvalidAgentRunError } from './services/agent-runs.js';
17
17
  export { InvalidScaffoldError } from './services/projects.js';
18
+ export { InvalidShareError } from './services/share.js';
19
+ export { InvalidTriageError, SyncUnavailableError, SyncUnauthorizedError, type SyncRemote, type SyncService, } from './services/sync.js';
18
20
  export declare const version: () => string;
19
21
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ export { InvalidCommentError } from './services/comments.js';
9
9
  export { InvalidCanvasError } from './services/canvas.js';
10
10
  export { InvalidAgentRunError } from './services/agent-runs.js';
11
11
  export { InvalidScaffoldError } from './services/projects.js';
12
+ export { InvalidShareError } from './services/share.js';
13
+ export { InvalidTriageError, SyncUnavailableError, SyncUnauthorizedError, } from './services/sync.js';
12
14
  import { readFileSync } from 'node:fs';
13
15
  import { dirname, join } from 'node:path';
14
16
  import { fileURLToPath } from 'node:url';
@@ -0,0 +1,51 @@
1
+ import { type Db, type Share } from '@plandesk/db';
2
+ export type SharePolicy = {
3
+ tasks: 'all' | string[];
4
+ documentIds: string[];
5
+ fields: {
6
+ assignee?: boolean;
7
+ description?: boolean;
8
+ };
9
+ };
10
+ export type ClientViewTask = {
11
+ id: string;
12
+ label: string;
13
+ status: string;
14
+ due_date: string | null;
15
+ x: number;
16
+ y: number;
17
+ description?: string | null;
18
+ assignee?: string | null;
19
+ };
20
+ export type ClientView = {
21
+ project: {
22
+ id: string;
23
+ name: string;
24
+ description: string | null;
25
+ updated_at: string;
26
+ };
27
+ tasks: ClientViewTask[];
28
+ edges: Array<{
29
+ id: string;
30
+ from: string;
31
+ to: string;
32
+ label: string | null;
33
+ }>;
34
+ documents: Array<{
35
+ id: string;
36
+ title: string;
37
+ body_html: string | null;
38
+ updated_at: string;
39
+ }>;
40
+ progress: Record<string, number>;
41
+ share: {
42
+ audience_name: string;
43
+ permissions: {
44
+ read: boolean;
45
+ submit: boolean;
46
+ };
47
+ expires_at: string | null;
48
+ };
49
+ };
50
+ export declare function buildClientView(db: Db, projectId: string, share: Share): ClientView | undefined;
51
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { getProject, listDocuments, listEdges, listTasks, parseSharePermissions, parseSharePolicy, } from '@plandesk/db';
2
+ function buildProgress(tasks) {
3
+ const progress = {};
4
+ for (const task of tasks) {
5
+ progress[task.status] = (progress[task.status] ?? 0) + 1;
6
+ }
7
+ return progress;
8
+ }
9
+ export function buildClientView(db, projectId, share) {
10
+ const project = getProject(db, projectId);
11
+ if (!project) {
12
+ return undefined;
13
+ }
14
+ const policy = parseSharePolicy(share);
15
+ const permissions = parseSharePermissions(share);
16
+ const allTasks = listTasks(db, projectId);
17
+ const sharedTasks = [];
18
+ for (const task of allTasks) {
19
+ const included = policy.tasks === 'all' || (Array.isArray(policy.tasks) && policy.tasks.includes(task.id));
20
+ if (!included) {
21
+ continue;
22
+ }
23
+ const viewTask = {
24
+ id: task.id,
25
+ label: task.label,
26
+ status: task.status,
27
+ due_date: task.dueDate?.toISOString() ?? null,
28
+ x: task.x,
29
+ y: task.y,
30
+ };
31
+ if (policy.fields.description) {
32
+ viewTask.description = task.description;
33
+ }
34
+ if (policy.fields.assignee) {
35
+ viewTask.assignee = task.assignee;
36
+ }
37
+ sharedTasks.push(viewTask);
38
+ }
39
+ const sharedTaskIds = new Set(sharedTasks.map((task) => task.id));
40
+ const sharedEdges = listEdges(db, projectId)
41
+ .filter((edge) => sharedTaskIds.has(edge.fromTaskId) && sharedTaskIds.has(edge.toTaskId))
42
+ .map((edge) => ({
43
+ id: edge.id,
44
+ from: edge.fromTaskId,
45
+ to: edge.toTaskId,
46
+ label: edge.label,
47
+ }));
48
+ const documentIdSet = new Set(policy.documentIds);
49
+ const sharedDocuments = listDocuments(db, projectId)
50
+ .filter((doc) => documentIdSet.has(doc.id))
51
+ .map((doc) => ({
52
+ id: doc.id,
53
+ title: doc.title,
54
+ body_html: doc.body,
55
+ updated_at: doc.updatedAt.toISOString(),
56
+ }));
57
+ return {
58
+ project: {
59
+ id: project.id,
60
+ name: project.name,
61
+ description: project.description,
62
+ updated_at: project.updatedAt.toISOString(),
63
+ },
64
+ tasks: sharedTasks,
65
+ edges: sharedEdges,
66
+ documents: sharedDocuments,
67
+ progress: buildProgress(sharedTasks),
68
+ share: {
69
+ audience_name: share.audienceName,
70
+ permissions,
71
+ expires_at: share.expiresAt?.toISOString() ?? null,
72
+ },
73
+ };
74
+ }
75
+ //# sourceMappingURL=projection.js.map
@@ -7,6 +7,8 @@ import { type ProjectService } from './projects.js';
7
7
  import { type AgentRunService } from './agent-runs.js';
8
8
  import { type TaskService } from './tasks.js';
9
9
  import { type TokenService } from './tokens.js';
10
+ import { type ShareService } from './share.js';
11
+ import { type SyncService } from './sync.js';
10
12
  export type ServicesDeps = {
11
13
  db: Db;
12
14
  eventBus?: EventBus;
@@ -20,6 +22,8 @@ export type Services = {
20
22
  commentService: CommentService;
21
23
  agentRunService: AgentRunService;
22
24
  tokenService: TokenService;
25
+ shareService: ShareService;
26
+ syncService: SyncService;
23
27
  };
24
28
  export declare function createServices(deps: ServicesDeps): Services;
25
29
  //# sourceMappingURL=index.d.ts.map
@@ -6,6 +6,8 @@ import { createProjectService } from './projects.js';
6
6
  import { createAgentRunService } from './agent-runs.js';
7
7
  import { createTaskService } from './tasks.js';
8
8
  import { createTokenService } from './tokens.js';
9
+ import { createShareService } from './share.js';
10
+ import { createSyncService } from './sync.js';
9
11
  export function createServices(deps) {
10
12
  const eventBus = deps.eventBus ?? createEventBus();
11
13
  const projectService = createProjectService({ db: deps.db, eventBus });
@@ -15,6 +17,8 @@ export function createServices(deps) {
15
17
  const commentService = createCommentService({ db: deps.db, eventBus });
16
18
  const agentRunService = createAgentRunService({ db: deps.db, eventBus });
17
19
  const tokenService = createTokenService({ db: deps.db });
20
+ const shareService = createShareService({ db: deps.db, eventBus });
21
+ const syncService = createSyncService({ db: deps.db, eventBus, taskService, shareService });
18
22
  return {
19
23
  eventBus,
20
24
  projectService,
@@ -24,6 +28,8 @@ export function createServices(deps) {
24
28
  commentService,
25
29
  agentRunService,
26
30
  tokenService,
31
+ shareService,
32
+ syncService,
27
33
  };
28
34
  }
29
35
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- import { clearDocumentParentRefsByProject, createDocument, createEdge, createProject as dbCreateProject, createTask, deleteAgentRun, deleteAgentRunEventsByRunId, deleteCommentsByProjectId, deleteDocumentsByProjectId, deleteEdgesByProjectId, deleteProject as dbDeleteProject, deleteTasksByProjectId, getProject as dbGetProject, InvalidTaskStatusError, isTaskStatus, listAgentRuns, listProjects as dbListProjects, listTasks, updateProject as dbUpdateProject, } from '@plandesk/db';
1
+ import { clearDocumentParentRefsByProject, createDocument, createEdge, createProject as dbCreateProject, createTask, deleteAgentRun, deleteAgentRunEventsByRunId, deleteCommentsByProjectId, deleteDocumentsByProjectId, deleteEdgesByProjectId, deleteShareSubmissionsByProjectId, deleteSharesByProjectId, deleteSyncRemoteByProjectId, deleteSyncStateByProjectId, deleteProject as dbDeleteProject, deleteTasksByProjectId, getProject as dbGetProject, InvalidTaskStatusError, isTaskStatus, listAgentRuns, listProjects as dbListProjects, listTasks, updateProject as dbUpdateProject, } from '@plandesk/db';
2
2
  import { emptyTaskStatusSummary, serializeDocument, serializeEdge, serializeProject, serializeProjectDetail, serializeTask, } from '../serialize.js';
3
3
  export class InvalidScaffoldError extends Error {
4
4
  constructor(message) {
@@ -90,6 +90,10 @@ export function createProjectService(deps) {
90
90
  deleteCommentsByProjectId(tx, id);
91
91
  deleteDocumentsByProjectId(tx, id);
92
92
  deleteTasksByProjectId(tx, id);
93
+ deleteShareSubmissionsByProjectId(tx, id);
94
+ deleteSyncStateByProjectId(tx, id);
95
+ deleteSyncRemoteByProjectId(tx, id);
96
+ deleteSharesByProjectId(tx, id);
93
97
  dbDeleteProject(tx, id);
94
98
  });
95
99
  eventBus.emit({ type: 'canvas_updated', projectId: id });
@@ -0,0 +1,41 @@
1
+ import { type Db, type Share, type ShareMode, type SharePermissions } from '@plandesk/db';
2
+ import type { EventBus } from '../events.js';
3
+ import { type ClientView, type SharePolicy } from '../projection.js';
4
+ export declare class InvalidShareError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export type ShareServiceDeps = {
8
+ db: Db;
9
+ eventBus: EventBus;
10
+ };
11
+ export type SerializedShare = {
12
+ id: string;
13
+ project_id: string;
14
+ audience_name: string;
15
+ mode: ShareMode;
16
+ permissions: SharePermissions;
17
+ policy: SharePolicy;
18
+ expires_at: string | null;
19
+ revoked_at: string | null;
20
+ created_at: string;
21
+ };
22
+ export declare function serializeShare(share: Share): SerializedShare;
23
+ export type CreateShareInput = {
24
+ audienceName: string;
25
+ mode: ShareMode;
26
+ permissions?: SharePermissions;
27
+ policy?: SharePolicy;
28
+ invitedEmails?: string[];
29
+ expiresAt?: Date;
30
+ };
31
+ export declare function createShareService(deps: ShareServiceDeps): {
32
+ createShare(projectId: string, input: CreateShareInput): {
33
+ share: SerializedShare;
34
+ token: string;
35
+ } | undefined;
36
+ listShares(projectId: string): SerializedShare[] | undefined;
37
+ revokeShare(id: string): boolean;
38
+ buildClientView(projectId: string, shareId: string): ClientView | undefined;
39
+ };
40
+ export type ShareService = ReturnType<typeof createShareService>;
41
+ //# sourceMappingURL=share.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { createShare as dbCreateShare, getProject, getShare as dbGetShare, listShares as dbListShares, parseSharePermissions, parseSharePolicy, revokeShare as dbRevokeShare, } from '@plandesk/db';
2
+ import { buildClientView } from '../projection.js';
3
+ export class InvalidShareError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'InvalidShareError';
7
+ }
8
+ }
9
+ const DEFAULT_POLICY = { tasks: 'all', documentIds: [], fields: {} };
10
+ const DEFAULT_PERMISSIONS = { read: true, submit: false };
11
+ export function serializeShare(share) {
12
+ return {
13
+ id: share.id,
14
+ project_id: share.projectId,
15
+ audience_name: share.audienceName,
16
+ mode: share.mode,
17
+ permissions: parseSharePermissions(share),
18
+ policy: parseSharePolicy(share),
19
+ expires_at: share.expiresAt?.toISOString() ?? null,
20
+ revoked_at: share.revokedAt?.toISOString() ?? null,
21
+ created_at: share.createdAt.toISOString(),
22
+ };
23
+ }
24
+ export function createShareService(deps) {
25
+ const { db } = deps;
26
+ return {
27
+ createShare(projectId, input) {
28
+ const project = getProject(db, projectId);
29
+ if (!project) {
30
+ return undefined;
31
+ }
32
+ if (input.audienceName.trim() === '') {
33
+ throw new InvalidShareError('Share audience name must not be empty');
34
+ }
35
+ const { share, token } = dbCreateShare(db, {
36
+ projectId,
37
+ audienceName: input.audienceName,
38
+ mode: input.mode,
39
+ permissions: input.permissions ?? DEFAULT_PERMISSIONS,
40
+ policy: input.policy ?? DEFAULT_POLICY,
41
+ invitedEmails: input.invitedEmails,
42
+ expiresAt: input.expiresAt,
43
+ });
44
+ return { share: serializeShare(share), token };
45
+ },
46
+ listShares(projectId) {
47
+ const project = getProject(db, projectId);
48
+ if (!project) {
49
+ return undefined;
50
+ }
51
+ return dbListShares(db, projectId).map(serializeShare);
52
+ },
53
+ revokeShare(id) {
54
+ return dbRevokeShare(db, id) !== undefined;
55
+ },
56
+ buildClientView(projectId, shareId) {
57
+ const share = dbGetShare(db, shareId);
58
+ if (!share || share.projectId !== projectId) {
59
+ return undefined;
60
+ }
61
+ return buildClientView(db, projectId, share);
62
+ },
63
+ };
64
+ }
65
+ //# sourceMappingURL=share.js.map
@@ -0,0 +1,74 @@
1
+ import { type Db, type ShareSubmission, type ShareSubmissionStatus, type TaskStatus } from '@plandesk/db';
2
+ import type { EventBus } from '../events.js';
3
+ import type { ShareService } from './share.js';
4
+ import type { TaskService } from './tasks.js';
5
+ export declare class SyncUnavailableError extends Error {
6
+ constructor(message?: string);
7
+ }
8
+ export declare class SyncUnauthorizedError extends Error {
9
+ constructor(message?: string);
10
+ }
11
+ export declare class InvalidTriageError extends Error {
12
+ constructor(message?: string);
13
+ }
14
+ export type SyncRemote = {
15
+ serverUrl: string;
16
+ globalProjectId: string;
17
+ syncToken: string;
18
+ };
19
+ export type SerializedSubmission = {
20
+ id: string;
21
+ project_id: string;
22
+ hosted_share_id: string;
23
+ participant_name: string;
24
+ title: string;
25
+ body: string | null;
26
+ severity: string | null;
27
+ task_ref: string | null;
28
+ status: ShareSubmissionStatus;
29
+ linked_task_id: string | null;
30
+ created_at: string;
31
+ pulled_at: string;
32
+ };
33
+ export declare function serializeSubmission(row: ShareSubmission): SerializedSubmission;
34
+ export type SyncServiceDeps = {
35
+ db: Db;
36
+ eventBus: EventBus;
37
+ taskService: TaskService;
38
+ shareService: ShareService;
39
+ };
40
+ export type WatchPushController = {
41
+ onChange(): void;
42
+ dispose(): void;
43
+ };
44
+ export type WatchPushOptions = {
45
+ debounceMs?: number;
46
+ onPushError?: (err: unknown) => void;
47
+ };
48
+ export declare function createSyncService(deps: SyncServiceDeps): {
49
+ push(projectId: string, remote: SyncRemote): Promise<{
50
+ pushed: number;
51
+ }>;
52
+ publishProject(projectId: string, input: {
53
+ serverUrl: string;
54
+ syncToken: string;
55
+ }): Promise<{
56
+ globalProjectId: string;
57
+ pushed: number;
58
+ }>;
59
+ pull(projectId: string, remote: SyncRemote): Promise<{
60
+ pulled: number;
61
+ }>;
62
+ listTriage(projectId: string, status?: ShareSubmissionStatus): SerializedSubmission[];
63
+ getSubmission(submissionId: string): SerializedSubmission | undefined;
64
+ setRemote(projectId: string, remote: SyncRemote): void;
65
+ getRemote(projectId: string): SyncRemote | undefined;
66
+ triage(submissionId: string, action: "accept" | "reject", remote: SyncRemote, asTask?: {
67
+ label?: string;
68
+ status?: TaskStatus;
69
+ description?: string;
70
+ }): Promise<SerializedSubmission>;
71
+ watchPush(projectId: string, remote: SyncRemote, opts?: WatchPushOptions): WatchPushController;
72
+ };
73
+ export type SyncService = ReturnType<typeof createSyncService>;
74
+ //# sourceMappingURL=sync.d.ts.map