streamby-core 0.41.0 → 0.43.0

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 (46) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +143 -8
  3. package/dist/adapters/database/connectionManager.js +33 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.js +5 -1
  6. package/dist/mcp/tools.js +93 -11
  7. package/dist/mcp/workflowNodes.js +9 -3
  8. package/dist/middleware/createRouter.js +6 -0
  9. package/dist/middleware/routes/connection.js +5 -2
  10. package/dist/middleware/routes/dbConnection.js +29 -5
  11. package/dist/middleware/routes/document.d.ts +3 -0
  12. package/dist/middleware/routes/document.js +348 -0
  13. package/dist/middleware/routes/export.d.ts +28 -0
  14. package/dist/middleware/routes/export.js +8 -4
  15. package/dist/middleware/routes/pipeline.js +5 -2
  16. package/dist/middleware/routes/project.js +17 -12
  17. package/dist/middleware/routes/projectResources.d.ts +8 -0
  18. package/dist/middleware/routes/projectResources.js +51 -0
  19. package/dist/middleware/routes/rights.js +72 -52
  20. package/dist/middleware/routes/storage.js +35 -2
  21. package/dist/middleware/routes/storageConnection.js +38 -15
  22. package/dist/middleware/routes/userIntegration.js +42 -1
  23. package/dist/middleware/routes/workflow.js +120 -37
  24. package/dist/models/Model.js +12 -1
  25. package/dist/services/apiTokenPermissions.d.ts +1 -1
  26. package/dist/services/apiTokenPermissions.js +10 -0
  27. package/dist/services/auth.d.ts +2 -0
  28. package/dist/services/auth.js +17 -1
  29. package/dist/services/creditsSheets.d.ts +39 -0
  30. package/dist/services/creditsSheets.js +129 -0
  31. package/dist/services/dependents.d.ts +11 -0
  32. package/dist/services/dependents.js +81 -0
  33. package/dist/services/documents.d.ts +66 -0
  34. package/dist/services/documents.js +347 -0
  35. package/dist/services/executor.js +8 -0
  36. package/dist/services/oauthTokens.js +2 -2
  37. package/dist/services/production.d.ts +1 -1
  38. package/dist/services/production.js +2 -2
  39. package/dist/services/projectIntegration.d.ts +14 -0
  40. package/dist/services/projectIntegration.js +32 -0
  41. package/dist/services/rights.d.ts +44 -0
  42. package/dist/services/rights.js +123 -0
  43. package/dist/services/workflowResources.d.ts +90 -0
  44. package/dist/services/workflowResources.js +288 -0
  45. package/dist/types/index.d.ts +45 -0
  46. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,94 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.43.0
4
+
5
+ ### Added
6
+
7
+ - **License metadata of assets**, for crediting the ones taken from others: `sourceUrl`,
8
+ `creatorName`, `creatorUrl`, `licenseType` and `comments`, all optional, in `asset_rights`.
9
+ The license is one of a closed list (`LICENSE_TYPES`: CC BY, CC BY-SA, CC BY-ND, CC BY-NC,
10
+ CC BY-NC-SA, CC BY-NC-ND, CC0, Free Standard, Standard, Editorial), checked by the server; the
11
+ links are http(s) URLs. It can be given with an upload (query params of `GET .../upload-url`;
12
+ an API token needs `rights:write` too). An `assetRightsEvent` to the project's active members
13
+ on each change, and when an asset is deleted.
14
+ - **Credits sheets**: project documents of a new kind, `credits`, that a workflow's Credits sheet
15
+ node makes (`CREDITS_SHEET_NODE`). One keeps only its sources (assets and Storage folders picked,
16
+ and the Ingest nodes connected to its node); its rows are made when it's read, from each asset's
17
+ metadata, with its status: complete when it has an asset and each has its creator, source and
18
+ license. Routes `GET /projects/:projectId/credits-sheets` and `.../credits-sheets/:docId`
19
+ (`rights:read`). Types `CreditsSheet`, `SheetRow`, `SheetSources` and `SheetStatus`.
20
+ - MCP: `get_credits_sheet`; `request_asset_upload` takes the license metadata.
21
+ - **The workflow defines the project.** Each resource is a node of it, bound by the resource's
22
+ id in its data. A resource added or removed from its section (`POST`/`DELETE` of a connection,
23
+ an API, an export, a pipeline, a project integration) adds or removes its node; saving the
24
+ workflow connects what its nodes were set to (`"__connect__:<integrationId>"`), makes the APIs
25
+ drafted on them (`data.connectionDraft`) and removes the resources whose nodes are gone,
26
+ answering `{ data, project, errors }`. A save needs `baseUpdatedAt` (a 409 over a newer version)
27
+ and a `nodeSchema` with `nodes` and `edges` lists (a 400 otherwise); an app connected with OAuth
28
+ can't remove resources through it (a 403 listing them). Pipelines and exports drafted on nodes
29
+ are made on save too. A node with a configuration of its own (a data source's table, an
30
+ Ingest's file) consumes its resource and is never merged into its node.
31
+ A workflow made before this is brought to it once, when it's read (`resourceNodesAt`).
32
+ - **What depends on a resource**, asked before removing it: `GET /projects/:id/dependents/:kind/:resourceId`
33
+ (connections, exports, pipelines): the workflow's, exports' and pipelines' nodes that use it,
34
+ and the files, folders, deliverables and shots kept for it. `GET /user/integrations/:id/dependents`:
35
+ the project connections that use an account's integration.
36
+ - **A change to a project's resources answers its list as it is then**, for the client to show
37
+ without asking again: connecting or disconnecting a database (`dbConnections`), a storage
38
+ (`storageConnections`, with built-ins' `available`) or an API (`connections`), deleting an
39
+ export (`exports`) or a pipeline (`pipelines`); `POST`/`DELETE /projects/:id/integrations`
40
+ answer the `project`; deleting an account's integration, each project it was disconnected
41
+ from (`affected`).
42
+ - **A built-in connected again keeps the id it had** (`disconnectedBuiltins` on the project; a
43
+ new `disconnectedBuiltins` column in a PostgreSQL projects table, added at startup), so its
44
+ files and records are its own again.
45
+
46
+ ### Changed
47
+
48
+ - **`asset_rights` holds the license metadata only.** `rightsHolder`, `licenseUrl`, `territory`,
49
+ `expiresAt`, `usageRestrictions` and `notes` are gone: not read, and dropped from a record when
50
+ it's next saved; a `licenseType` off the list reads as none. `set_asset_rights` and
51
+ `get_asset_rights` take and give the new fields.
52
+
53
+ ### Fixed
54
+
55
+ - Deleting an asset or a project deletes its license metadata (it was left behind).
56
+ - A built-in database or storage can be disconnected from a project (it was refused with a 403,
57
+ though the backfill already left a disconnected one alone): what the project keeps in it stays.
58
+ - Deleting an account's integration disconnects the project connections that use it (they were
59
+ left pointing at nothing).
60
+ - `POST .../connections/db` and `.../connections/storage` with an `integrationId` connect a
61
+ built-in too (they refused it as an unknown integration, and checked the pool's `sql`/`nosql`
62
+ as the type): with an integration, its name and type are the integration's.
63
+
64
+ ## 0.42.0
65
+
66
+ ### Added
67
+
68
+ - **Project documents**: markdown a project's team reads and edits in the UI, and agents read and
69
+ keep up to date through the MCP server, with every save a version (who saved it, and when). The
70
+ README's Documents section documents them.
71
+ - **New tables: `documents` and `document_versions`.** They're created at startup in the main
72
+ database (collections in MongoDB; in memory without one).
73
+ - Routes under `/projects/:projectId/documents`: the tree, a document, creating, a new version
74
+ (`PATCH` with `baseVersion`; 409 with the document as it is now when it changed since), a
75
+ section's (`PATCH .../sections`), moving and marking exportable, the history
76
+ (`.../versions[/:version]`), publishing a version (`POST .../publish`) and deleting (a folder
77
+ with everything in it). Deleting a project deletes its documents.
78
+ - Permissions `docs:read`, `docs:write`, `docs:publish` and `docs:delete`. Publishing (a
79
+ version for the exports, or whether they deliver it: `POST .../publish`, `PATCH` with
80
+ `exportable`) needs `docs:publish`, not `docs:write`. The OAuth scopes grant reading
81
+ (`streamby:read`) and writing (`streamby:write`); an app publishes and deletes none, whatever
82
+ its scopes: what it edits stays a draft.
83
+ - In MongoDB, a save writes the document and its version in a transaction when the server has
84
+ them (a replica set or a sharded cluster); on a standalone server, two writes.
85
+ - MCP tools `list_documents`, `get_document`, `list_document_versions`, `create_document`,
86
+ `update_document` and `update_document_section`.
87
+ - An export's `documentsNode` delivers the published version of the project's exportable
88
+ documents (all, one folder's, or some), never a draft.
89
+ - A `documentEvent` to the project's active members on each change.
90
+ - Types `ProjectDocument`, `DocumentVersion`, `DocumentAuthor` and `DocumentKind`.
91
+
3
92
  ## 0.41.0
4
93
 
5
94
  ### Added
package/README.md CHANGED
@@ -132,8 +132,9 @@ hash is stored, with a short prefix to tell it apart.
132
132
  | Production | `production:read`, `production:write`, `production:delete` | Sequences, shots and tasks. |
133
133
  | Workflow | `workflow:read`, `workflow:write` | The workflow and its pipelines. |
134
134
  | Exports | `exports:read`, `exports:write` | The project's exports. |
135
- | Rights | `rights:read`, `rights:write` | Assets' rights. |
135
+ | Rights | `rights:read`, `rights:write` | Assets' license metadata, and reading credits sheets. |
136
136
  | Jobs | `jobs:read`, `jobs:run` | Following jobs; starting them (transcoding, rendering, AI, publishing a deliverable). |
137
+ | Documents | `docs:read`, `docs:write`, `docs:publish`, `docs:delete` | The project's documents: reading them and their history; creating, editing and moving them; publishing them (the version its exports deliver, and whether they deliver it); deleting them. |
137
138
  | Account | `projects:create` | Creating projects. A user's token only; the token reaches what it creates. |
138
139
 
139
140
  `PROJECT_PERMISSIONS` and `ACCOUNT_PERMISSIONS` are exported. A route no permission covers
@@ -185,15 +186,16 @@ approved, and never more than the user can: their role in each project still app
185
186
 
186
187
  | Scope | Permissions |
187
188
  |---|---|
188
- | `streamby:read` | Every `:read` permission: `project:read`, `members:read`, `assets:read`, `production:read`, `workflow:read`, `exports:read`, `rights:read`, `jobs:read`. |
189
- | `streamby:write` | Those, and making and changing: `project:update`, `assets:write`, `production:write`, `workflow:write`, `exports:write`, `rights:write`, `jobs:run`, `projects:create`. |
189
+ | `streamby:read` | Every `:read` permission: `project:read`, `members:read`, `assets:read`, `production:read`, `workflow:read`, `exports:read`, `rights:read`, `jobs:read`, `docs:read`. |
190
+ | `streamby:write` | Those, and making and changing: `project:update`, `assets:write`, `production:write`, `workflow:write`, `exports:write`, `rights:write`, `jobs:run`, `projects:create`, `docs:write`. |
190
191
 
191
192
  No scope grants deleting (`*:delete`, `project:delete`) nor managing members. And since some
192
193
  write permissions also reach routes that delete (`project:update` a connection, a worker or an
193
194
  integration; `workflow:write` a pipeline; `exports:write` an export), **an app's `DELETE` is
194
195
  refused (403) whatever its route**, but taking a deliverable's mark off, which only forgets the
195
- mark (the asset stays). API tokens aren't affected. Other scopes (`profile`, `email`) grant
196
- nothing in StreamBy. `OAUTH_SCOPE_PERMISSIONS` is exported.
196
+ mark (the asset stays). Nor does an app publish a document (`POST .../publish`, or a `PATCH`
197
+ changing `exportable`), whatever its scopes: what it edits stays a draft until someone publishes
198
+ it. API tokens aren't affected. Other scopes (`profile`, `email`) grant nothing in StreamBy. `OAUTH_SCOPE_PERMISSIONS` is exported.
197
199
 
198
200
  **Telling clients where to sign in**
199
201
 
@@ -283,7 +285,7 @@ A token sees only the tools whose permissions it has (an app: those its scopes g
283
285
  | `list_assets` (by category, optionally in one storage connection, and filtered by name), `get_asset_metadata` | `assets:read` |
284
286
  | `update_asset_metadata` | `assets:write` |
285
287
  | `delete_asset` | `assets:delete` |
286
- | `get_asset_rights` / `set_asset_rights` | `rights:read` / `rights:write` |
288
+ | `get_asset_rights`, `get_credits_sheet` / `set_asset_rights` | `rights:read` / `rights:write` |
287
289
  | `get_workflow`, `list_pipelines`, `get_pipeline` | `workflow:read` |
288
290
  | `create_pipeline`, `update_pipeline` | `workflow:write` |
289
291
  | `list_exports` | `project:read` and `exports:read` |
@@ -296,7 +298,9 @@ A token sees only the tools whose permissions it has (an app: those its scopes g
296
298
  | `create_sequence`, `create_shot`, `create_task` | `production:write` and `workflow:write` |
297
299
  | `delete_sequence`, `delete_shot`, `delete_task` | `production:delete` |
298
300
  | `list_asset_versions`, `get_asset_download_url`, `list_deliverables` | `assets:read` |
299
- | `request_asset_upload`, `mark_deliverable`, `remove_deliverable` | `assets:write` |
301
+ | `request_asset_upload` (with license metadata, also `rights:write`), `mark_deliverable`, `remove_deliverable` | `assets:write` |
302
+ | `list_documents`, `get_document`, `list_document_versions` | `docs:read` |
303
+ | `create_document`, `update_document`, `update_document_section` | `docs:write` |
300
304
 
301
305
  The tools take the path a user does in the UI, and each one's description says where its
302
306
  result shows there. Production data lives under a production node of the project's workflow:
@@ -446,6 +450,87 @@ person removes it in their account's connected apps (in Nhexa, accounts.nhexa.cl
446
450
 
447
451
  ---
448
452
 
453
+ ## 📄 Documents
454
+
455
+ Each project keeps its documents: markdown its team reads and edits (its concept, its
456
+ architecture, its decisions), and agents read and keep up to date through the MCP server. They're
457
+ kept in the main database (the `documents` and `document_versions` tables, created at startup,
458
+ or collections), each one in a single project, in folders.
459
+
460
+ - **Every save is a version**, with who saved it and when: a user, an API token, or an app on a
461
+ user's behalf (`{ userId, username, via: 'session' | 'apiToken' | 'oauth', tokenId?, clientId?, clientName? }`).
462
+ - **Nothing is overwritten without notice.** A change says the version it was made on
463
+ (`baseVersion`); if someone saved another one since, it's refused with a 409 and the document
464
+ as it is now, to read again.
465
+ - **A section** is what's under a heading (`## Architecture`), up to the next one of the same
466
+ level or above: it can be replaced alone, the rest untouched.
467
+ - **Drafts and published versions.** A document marked exportable delivers, through the project's
468
+ exports, only the version published from its history; editing it changes nothing exported until
469
+ another version is published. An export's `documentsNode` (on the StreamBy node's data input)
470
+ delivers them, `[{ id, title, parentId, position, version, content, publishedAt }]`: all the
471
+ project's, those in one folder (`data.folderId`), or some (`data.documentIds`).
472
+ - **In real time**, a `documentEvent` reaches the project's active members on each change:
473
+ `{ projectId, action: 'created' | 'saved' | 'changed' | 'deleted', documentIds, version?, title?, author }`
474
+ (`saved` is a new version; `changed`, a move or a publication).
475
+
476
+ | Route | Permission |
477
+ |---|---|
478
+ | `GET /projects/:projectId/documents`: the tree, without contents | `docs:read` |
479
+ | `POST /projects/:projectId/documents` `{ title, content?, kind?: 'document' \| 'folder' \| 'credits', parentId?, position? }` | `docs:write` |
480
+ | `GET /projects/:projectId/documents/:docId` | `docs:read` |
481
+ | `PATCH /projects/:projectId/documents/:docId` `{ baseVersion, title?, content? }` (a new version) and/or `{ parentId?, position? }` | `docs:write` |
482
+ | `PATCH /projects/:projectId/documents/:docId` `{ exportable }` | `docs:write` and `docs:publish` |
483
+ | `PATCH /projects/:projectId/documents/:docId/sections` `{ baseVersion, heading, content }` | `docs:write` |
484
+ | `GET /projects/:projectId/documents/:docId/versions` and `/versions/:version` | `docs:read` |
485
+ | `POST /projects/:projectId/documents/:docId/publish` `{ version }` (`null`: none) | `docs:publish` |
486
+ | `DELETE /projects/:projectId/documents/:docId` (a folder, with everything in it) | `docs:delete` |
487
+
488
+ Editors and admins create, edit, publish and delete them; viewers read them. An app connected
489
+ with OAuth reads and edits them, but publishes and deletes none: what it edits stays a draft.
490
+
491
+ In MongoDB, a save writes the document and its version in a transaction when the server has them
492
+ (a replica set or a sharded cluster). A standalone server doesn't: there they're two writes.
493
+
494
+ ---
495
+
496
+ ## ©️ License metadata and credits sheets
497
+
498
+ Each asset can carry its **license metadata**, for all its versions: where it was taken from
499
+ (`sourceUrl`), who made it (`creatorName`, `creatorUrl`), its license (`licenseType`) and
500
+ `comments`. All are optional; the links are http(s) URLs, and the license is one of
501
+ `CC BY`, `CC BY-SA`, `CC BY-ND`, `CC BY-NC`, `CC BY-NC-SA`, `CC BY-NC-ND`, `CC0`, `Free Standard`,
502
+ `Standard` and `Editorial` (`LICENSE_TYPES`, exported). It's kept in the `asset_rights`
503
+ collection, the only place it lives, and goes with its asset or its project when they're deleted.
504
+ It can be given with the upload: `licenseType`, `sourceUrl`, `creatorName`, `creatorUrl` and
505
+ `comments` as query params of `GET .../upload-url` (an API token needs `rights:write` too).
506
+
507
+ A **credits sheet** is a project document of kind `credits`, made by a Credits sheet node of the
508
+ workflow (its `data.documentId`). It keeps only what feeds it, as its content:
509
+ `{ "assetIds": [...], "folderIds": [...] }` (assets and Storage folders picked, with everything
510
+ inside them); the Ingest nodes connected to its node feed it too, with the asset each imports
511
+ (`data.fileId`). Its rows are made each time it's read, from each asset's metadata, so two sheets
512
+ with the same asset show the same, and editing a row is setting its asset's metadata. Deleting
513
+ the node leaves the document; an asset deleted leaves the sheet.
514
+
515
+ A sheet is **complete** when it has an asset and each one has its creator, source and license;
516
+ **incomplete** otherwise, each row listing what it lacks (`missing`).
517
+
518
+ - **In real time**, an `assetRightsEvent` reaches the project's active members on each change:
519
+ `{ projectId, action: 'saved' | 'deleted', assetIds, rights? }` (`deleted`: the asset is gone).
520
+
521
+ | Route | Permission |
522
+ |---|---|
523
+ | `GET /projects/:projectId/assets/:assetId/rights` | `rights:read` |
524
+ | `PUT /projects/:projectId/assets/:assetId/rights` `{ sourceUrl?, creatorName?, creatorUrl?, licenseType?, comments? }` (the fields given; empty or `null` clears one) | `rights:write` |
525
+ | `GET /projects/:projectId/credits-sheets`: each with `{ id, title, status, rows, incomplete }` | `rights:read` |
526
+ | `GET /projects/:projectId/credits-sheets/:docId`: `{ document, sources, rows, status }` | `rights:read` |
527
+
528
+ Each row: `{ assetId, name, category, sourceUrl, creatorName, creatorUrl, licenseType, comments, missing, from }`,
529
+ `from` saying how it got there (`asset`, `folder`, `ingest`). Editors and admins set the metadata
530
+ (as they edit the asset); every member reads it.
531
+
532
+ ---
533
+
449
534
  ## ⚙️ Processing workers
450
535
 
451
536
  StreamBy doesn't process assets itself (optimizing a glTF, making LODs, transcoding...): it
@@ -551,6 +636,56 @@ A job its worker doesn't complete before its deadline is sent again, up to
551
636
 
552
637
  ---
553
638
 
639
+ ## 🧭 The workflow defines the project
640
+
641
+ Each of a project's resources (a database, an API, a storage, a service, an export, a pipeline)
642
+ is a node of its workflow, bound to it by the resource's id in its data (`connectionId`,
643
+ `storageConnectionId`, `integrationConnectionId`, `exportId`, `pipelineId`). One node per
644
+ resource; a data source reading one of a database's tables is a node of its own.
645
+
646
+ - **From a section** (or an agent): adding a resource adds its node to the saved workflow, and
647
+ removing it removes its nodes.
648
+ - **Saving the workflow** (`PATCH /projects/:id/workflow` `{ nodeSchema, baseUpdatedAt }`) makes
649
+ the project what it defines, then answers `{ data, project, errors }`: the workflow, the
650
+ project's lists as they are then, and what couldn't be done. A node set to an integration the
651
+ project doesn't have (`"__connect__:<integrationId>"`) connects it; an API, a pipeline or an
652
+ export drafted on a node (`data.connectionDraft`, `data.pipelineDraft`, `data.exportDraft`) is
653
+ made; a resource whose nodes are all gone is removed.
654
+ - `baseUpdatedAt` (the workflow's `updatedAt` as it was read) is required with a `nodeSchema`,
655
+ and `nodeSchema` must be `{ nodes: [...], edges: [...] }`: a 400 otherwise. Saved over a newer
656
+ version, it's a 409 with the workflow as it is now.
657
+ - An app connected with OAuth removes nothing through it, as it deletes nothing: a save that
658
+ would remove resources is a 403 listing them (`removing`).
659
+ - A node holding a configuration of its own beside its resource's id (a data source reading one
660
+ table, an Ingest importing one file) consumes the resource: never merged into its node.
661
+ - **Reading it** (`GET`) never rewrites it, except once, for a workflow made before this: the
662
+ nodes once drawn from the project's lists are bound by their ids, a resource without a node gets
663
+ one, and two nodes of the same resource are merged.
664
+
665
+ ---
666
+
667
+ ## 🔌 Removing a project's resources
668
+
669
+ Before a connection (database, API, storage, service), an export or a pipeline is removed, the
670
+ UI asks what depends on it: `GET /projects/:id/dependents/:kind/:resourceId` (`kind`: `db`,
671
+ `api`, `storage`, `service`, `export`, `pipeline`; `project:read`) answers
672
+ `{ dependents: [{ type, name, id?, count? }] }`: the nodes that use it in the workflow, the
673
+ exports and the pipelines (`workflow`, `export`, `pipeline`: they stop working without it), and
674
+ what's kept for it (`files` and `folders` of a storage, `deliverables` and `shots` of an
675
+ export). Removing it changes none of them.
676
+
677
+ **Built-ins** (the host's database and storage) are disconnected like any connection, and what
678
+ the project keeps in them stays. They're connected again as any integration is, by its id in
679
+ the pool (`GET /user/integrations`): `POST /projects/:id/integrations` `{ integrationId }`, or
680
+ `POST .../connections/db` and `.../connections/storage` with `integrationId` (its name and type
681
+ come from the integration). One connected again keeps the id it had (`disconnectedBuiltins`
682
+ on the project), so its files and records are its own again.
683
+
684
+ Deleting an account's integration disconnects the project connections that use it;
685
+ `GET /user/integrations/:id/dependents` lists them first.
686
+
687
+ ---
688
+
554
689
  ## 👥 Project roles
555
690
 
556
691
  What a user can do in a project depends on their role in that project, as an active member
@@ -560,7 +695,7 @@ What a user can do in a project depends on their role in that project, as an act
560
695
  | Role | Can |
561
696
  |---|---|
562
697
  | `viewer` | See the project and its content, and archive the project in their own list. |
563
- | `editor` | Everything a viewer can, plus change the project: its details, connections, exports, files and folders, workflow, pipelines, production, reviews, rights, and start jobs. |
698
+ | `editor` | Everything a viewer can, plus change the project: its details, connections, exports, files and folders, workflow, pipelines, production, reviews, rights, documents, and start jobs. |
564
699
  | `admin` | Everything an editor can, plus manage the project's members and delete the project. |
565
700
 
566
701
  A project's public exports (`GET /streamby/:projectId/export/:exportName`) are read without a
@@ -101,6 +101,7 @@ const createTables = async (pool) => {
101
101
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS workflow JSONB;
102
102
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS pipelines JSONB DEFAULT '[]';
103
103
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "builtinBackfilledAt" TIMESTAMP WITH TIME ZONE;
104
+ ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS "disconnectedBuiltins" JSONB DEFAULT '[]';
104
105
  -- Written when a project is made (routes/project) and by its connection routes.
105
106
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS public BOOLEAN DEFAULT TRUE;
106
107
  ALTER TABLE streamby.projects ADD COLUMN IF NOT EXISTS category TEXT;
@@ -254,6 +255,38 @@ const createTables = async (pool) => {
254
255
  ALTER TABLE streamby.mcp_audit ALTER COLUMN "tokenId" DROP NOT NULL;
255
256
  `);
256
257
  console.log('✅ "mcp_audit" table ensured to exist.');
258
+ // Projects' documents and their versions (see services/documents). Main db only.
259
+ await pool.query(`
260
+ CREATE TABLE IF NOT EXISTS streamby.documents (
261
+ id TEXT PRIMARY KEY,
262
+ "projectId" VARCHAR(255) NOT NULL,
263
+ kind VARCHAR(20) NOT NULL DEFAULT 'document',
264
+ title TEXT NOT NULL,
265
+ "parentId" TEXT,
266
+ position INTEGER NOT NULL DEFAULT 0,
267
+ content TEXT NOT NULL DEFAULT '',
268
+ version INTEGER NOT NULL,
269
+ exportable BOOLEAN NOT NULL DEFAULT FALSE,
270
+ "publishedVersion" INTEGER,
271
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
272
+ "createdBy" JSONB NOT NULL,
273
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
274
+ "updatedBy" JSONB NOT NULL
275
+ );
276
+ CREATE INDEX IF NOT EXISTS documents_project_idx ON streamby.documents ("projectId");
277
+ CREATE TABLE IF NOT EXISTS streamby.document_versions (
278
+ "documentId" TEXT NOT NULL,
279
+ "projectId" VARCHAR(255) NOT NULL,
280
+ version INTEGER NOT NULL,
281
+ title TEXT NOT NULL,
282
+ content TEXT NOT NULL,
283
+ author JSONB NOT NULL,
284
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
285
+ PRIMARY KEY ("documentId", version)
286
+ );
287
+ CREATE INDEX IF NOT EXISTS document_versions_project_idx ON streamby.document_versions ("projectId");
288
+ `);
289
+ console.log('✅ "documents" and "document_versions" tables ensured to exist.');
257
290
  }
258
291
  catch (error) {
259
292
  console.error('❌ Error ensuring tables exist:', error);
package/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  export { createStreamByRouter } from './middleware/createRouter';
2
2
  export { emitToUser } from './services/wsHub';
3
3
  export { createNotification } from './services/notification';
4
- export type { UserPlan, UserSubscription, Auth, ApiTokenScope, ApiTokenKind, ApiTokenPermission, ProjectPermission, AccountPermission, OAuthClientInfo, OAuthResourceConfig } from './types';
4
+ export type { UserPlan, UserSubscription, Auth, ApiTokenScope, ApiTokenKind, ApiTokenPermission, ProjectPermission, AccountPermission, OAuthClientInfo, OAuthResourceConfig, ProjectDocument, DocumentVersion, DocumentAuthor, DocumentKind } from './types';
5
5
  export { resolveApiToken, API_TOKEN_PREFIX } from './services/apiTokens';
6
6
  export { PROJECT_PERMISSIONS, ACCOUNT_PERMISSIONS } from './services/apiTokenPermissions';
7
+ export { LICENSE_TYPES } from './services/rights';
8
+ export type { AssetRights, LicenseType } from './services/rights';
9
+ export { CREDITS_SHEET_NODE } from './services/creditsSheets';
10
+ export type { CreditsSheet, SheetRow, SheetSources, SheetStatus } from './services/creditsSheets';
7
11
  export type { ResolvedApiToken } from './services/apiTokens';
8
12
  export { createOAuthTokenVerifier, looksLikeOAuthAccessToken, protectedResourceMetadata, defaultResourceMetadataUrl, permissionsForOAuthScopes, OAUTH_SCOPES, OAUTH_SCOPE_PERMISSIONS, } from './services/oauthTokens';
9
13
  export type { VerifiedOAuthToken, OAuthTokenVerifierOptions, Jwks, Jwk, OAuthScope } from './services/oauthTokens';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OAUTH_SCOPE_PERMISSIONS = exports.OAUTH_SCOPES = exports.permissionsForOAuthScopes = exports.defaultResourceMetadataUrl = exports.protectedResourceMetadata = exports.looksLikeOAuthAccessToken = exports.createOAuthTokenVerifier = exports.ACCOUNT_PERMISSIONS = exports.PROJECT_PERMISSIONS = exports.API_TOKEN_PREFIX = exports.resolveApiToken = exports.createNotification = exports.emitToUser = exports.createStreamByRouter = void 0;
3
+ exports.OAUTH_SCOPE_PERMISSIONS = exports.OAUTH_SCOPES = exports.permissionsForOAuthScopes = exports.defaultResourceMetadataUrl = exports.protectedResourceMetadata = exports.looksLikeOAuthAccessToken = exports.createOAuthTokenVerifier = exports.CREDITS_SHEET_NODE = exports.LICENSE_TYPES = exports.ACCOUNT_PERMISSIONS = exports.PROJECT_PERMISSIONS = exports.API_TOKEN_PREFIX = exports.resolveApiToken = exports.createNotification = exports.emitToUser = exports.createStreamByRouter = void 0;
4
4
  var createRouter_1 = require("./middleware/createRouter");
5
5
  Object.defineProperty(exports, "createStreamByRouter", { enumerable: true, get: function () { return createRouter_1.createStreamByRouter; } });
6
6
  var wsHub_1 = require("./services/wsHub");
@@ -13,6 +13,10 @@ Object.defineProperty(exports, "API_TOKEN_PREFIX", { enumerable: true, get: func
13
13
  var apiTokenPermissions_1 = require("./services/apiTokenPermissions");
14
14
  Object.defineProperty(exports, "PROJECT_PERMISSIONS", { enumerable: true, get: function () { return apiTokenPermissions_1.PROJECT_PERMISSIONS; } });
15
15
  Object.defineProperty(exports, "ACCOUNT_PERMISSIONS", { enumerable: true, get: function () { return apiTokenPermissions_1.ACCOUNT_PERMISSIONS; } });
16
+ var rights_1 = require("./services/rights");
17
+ Object.defineProperty(exports, "LICENSE_TYPES", { enumerable: true, get: function () { return rights_1.LICENSE_TYPES; } });
18
+ var creditsSheets_1 = require("./services/creditsSheets");
19
+ Object.defineProperty(exports, "CREDITS_SHEET_NODE", { enumerable: true, get: function () { return creditsSheets_1.CREDITS_SHEET_NODE; } });
16
20
  var oauthTokens_1 = require("./services/oauthTokens");
17
21
  Object.defineProperty(exports, "createOAuthTokenVerifier", { enumerable: true, get: function () { return oauthTokens_1.createOAuthTokenVerifier; } });
18
22
  Object.defineProperty(exports, "looksLikeOAuthAccessToken", { enumerable: true, get: function () { return oauthTokens_1.looksLikeOAuthAccessToken; } });
package/dist/mcp/tools.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TOOLS = void 0;
4
4
  const zod_1 = require("zod");
5
+ const rights_1 = require("../services/rights");
5
6
  const tool = (definition) => definition;
6
7
  const id = (what) => zod_1.z.string().min(1).describe(`The ${what}'s id`);
7
8
  const projectId = id('project');
@@ -25,14 +26,13 @@ const taskFields = {
25
26
  dueDate: zod_1.z.string().optional().describe('An ISO 8601 date'),
26
27
  notes: zod_1.z.string().optional(),
27
28
  };
29
+ // An asset's license metadata: what crediting it takes. Each is optional; an empty one clears it.
28
30
  const rightsFields = {
29
- rightsHolder: zod_1.z.string().optional(),
30
- licenseType: zod_1.z.string().optional().describe('e.g. CC0, CC BY 4.0, proprietary'),
31
- licenseUrl: zod_1.z.string().optional(),
32
- territory: zod_1.z.string().optional(),
33
- expiresAt: zod_1.z.string().optional().describe('An ISO 8601 date'),
34
- usageRestrictions: zod_1.z.string().optional(),
35
- notes: zod_1.z.string().optional(),
31
+ sourceUrl: zod_1.z.string().optional().describe('Where the asset was taken from (an http(s) URL)'),
32
+ creatorName: zod_1.z.string().optional().describe('Who made it'),
33
+ creatorUrl: zod_1.z.string().optional().describe("The creator's page (an http(s) URL)"),
34
+ licenseType: zod_1.z.enum(rights_1.LICENSE_TYPES).or(zod_1.z.literal('')).optional().describe('Its license'),
35
+ comments: zod_1.z.string().optional(),
36
36
  };
37
37
  // Only the fields given (an unset optional field isn't sent as undefined).
38
38
  const defined = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
@@ -86,7 +86,7 @@ exports.TOOLS = [
86
86
  }),
87
87
  tool({
88
88
  name: 'get_asset_rights', title: "Get an asset's rights", permissions: ['rights:read'],
89
- description: "An asset's rights: holder, license, territory, expiry and restrictions.",
89
+ description: "An asset's license metadata: where it was taken from, its creator (name and link), its license and comments; null if it has none.",
90
90
  input: { projectId, assetId: id('asset') },
91
91
  projectOf: byProject,
92
92
  call: ({ projectId, assetId }) => ({ method: 'GET', path: `/projects/${enc(projectId)}/assets/${enc(assetId)}/rights` }),
@@ -164,7 +164,7 @@ exports.TOOLS = [
164
164
  }),
165
165
  tool({
166
166
  name: 'set_asset_rights', title: "Set an asset's rights", permissions: ['rights:write'], write: true,
167
- description: "Sets an asset's rights: holder, license, territory, expiry, restrictions and notes. They show in the file's Rights tab, in Storage.",
167
+ description: `Sets an asset's license metadata (the fields given; an empty one clears it): where it was taken from, its creator (name and link), its license (one of: ${rights_1.LICENSE_TYPES.join(', ')}) and comments. It shows in the file's Rights tab, in Storage, and in every credits sheet the asset is in.`,
168
168
  input: { projectId, assetId: id('asset'), ...rightsFields },
169
169
  projectOf: byProject,
170
170
  call: ({ projectId, assetId, ...rights }) => ({ method: 'PUT', path: `/projects/${enc(projectId)}/assets/${enc(assetId)}/rights`, body: defined(rights) }),
@@ -245,6 +245,7 @@ exports.TOOLS = [
245
245
  "It doesn't upload anything: the file is sent by the tool that has it (for example Blender's MCP, running Python), with an HTTP PUT of its bytes to `upload.url` and the `upload.headers` given, before `expiresAt`.",
246
246
  "The URL works for that file only. A new version keeps the asset's URL (it replaces what it serves); the earlier ones are archived and can still be downloaded.",
247
247
  "Categories: images, audios, videos, 3d-models (.glb, .gltf, .obj, .fbx, .stl, .ply); contentType must match it.",
248
+ "For a new asset taken from someone else (a CC model, a stock image), give its license metadata too, so it's credited (it needs the rights:write permission); for a new version, set_asset_rights changes it.",
248
249
  ].join(' '),
249
250
  input: {
250
251
  projectId,
@@ -253,10 +254,14 @@ exports.TOOLS = [
253
254
  contentType: zod_1.z.string().min(1).describe('Its MIME type (e.g. model/gltf-binary, image/png, video/mp4)'),
254
255
  assetId: zod_1.z.string().optional().describe('An existing asset to upload a new version of; a new asset if not given'),
255
256
  connectionId: zod_1.z.string().optional().describe("A storage connection to store it in; the project's built-in storage if not given"),
257
+ ...rightsFields,
256
258
  },
257
259
  projectOf: byProject,
258
- call: ({ projectId, category, fileName, contentType, assetId, connectionId }) => {
259
- const query = new URLSearchParams({ fileName, contentType, ...(connectionId && !assetId ? { category } : {}) });
260
+ call: ({ projectId, category, fileName, contentType, assetId, connectionId, ...rights }) => {
261
+ const query = new URLSearchParams({
262
+ fileName, contentType, ...(connectionId && !assetId ? { category } : {}),
263
+ ...(assetId ? {} : defined(rights)),
264
+ });
260
265
  const files = connectionId ? `/projects/${enc(projectId)}/connections/storage/${enc(connectionId)}` : `/projects/${enc(projectId)}/storage`;
261
266
  const path = assetId
262
267
  ? `${files}/files/${enc(assetId)}/replace-url`
@@ -358,6 +363,83 @@ exports.TOOLS = [
358
363
  call: ({ projectId, jobId }) => ({ method: 'POST', path: `/projects/${enc(projectId)}/jobs/${enc(jobId)}/retry` }),
359
364
  pick: (data) => data?.job,
360
365
  }),
366
+ // ── Credits sheets ──
367
+ tool({
368
+ name: 'get_credits_sheet', title: 'Read a credits sheet', permissions: ['rights:read'],
369
+ description: "A credits sheet (a document of kind 'credits', made by a Credits sheet node of the workflow; list_documents lists them): a row per asset fed to it (picked on it, in a Storage folder picked, or imported by an Ingest node connected to it) with that asset's license metadata as it is now, and its status: 'complete' when it has an asset and each has its creator, source and license; 'incomplete' otherwise, each row listing what it lacks (`missing`). To fill a row in, set_asset_rights on its asset.",
370
+ input: { projectId, documentId: id('credits sheet') },
371
+ projectOf: byProject,
372
+ call: ({ projectId, documentId }) => ({ method: 'GET', path: `/projects/${enc(projectId)}/credits-sheets/${enc(documentId)}` }),
373
+ pick: (data) => data && { id: data.document?.id, title: data.document?.title, status: data.status, rows: data.rows },
374
+ }),
375
+ // ── Documents ──
376
+ tool({
377
+ name: 'list_documents', title: 'List documents', permissions: ['docs:read'],
378
+ description: "A project's documents (its concept, architecture, decisions...), as the tree of its Documents section shows them: each one's id, title, folder (parentId; a folder's kind is 'folder'), position, current version and the version published through its exports, without their content (read one with get_document).",
379
+ input: { projectId },
380
+ projectOf: byProject,
381
+ call: ({ projectId }) => ({ method: 'GET', path: `/projects/${enc(projectId)}/documents` }),
382
+ pick: (data) => data?.documents ?? [],
383
+ }),
384
+ tool({
385
+ name: 'get_document', title: 'Read a document', permissions: ['docs:read'],
386
+ description: "A document's markdown and its current version, the one an update is made on. With `version`, that past version instead (see list_document_versions).",
387
+ input: { projectId, documentId: id('document'), version: zod_1.z.number().int().min(1).optional().describe('A past version to read; the current one if not given') },
388
+ projectOf: byProject,
389
+ call: ({ projectId, documentId, version }) => ({
390
+ method: 'GET',
391
+ path: `/projects/${enc(projectId)}/documents/${enc(documentId)}${version ? `/versions/${version}` : ''}`,
392
+ }),
393
+ pick: (data) => data?.document ?? data?.version,
394
+ }),
395
+ tool({
396
+ name: 'list_document_versions', title: "List a document's versions", permissions: ['docs:read'],
397
+ description: "A document's history, the latest first: each version's number, title, who saved it (a user, an API token, or an app on a user's behalf) and when.",
398
+ input: { projectId, documentId: id('document') },
399
+ projectOf: byProject,
400
+ call: ({ projectId, documentId }) => ({ method: 'GET', path: `/projects/${enc(projectId)}/documents/${enc(documentId)}/versions` }),
401
+ pick: (data) => data?.versions ?? [],
402
+ }),
403
+ tool({
404
+ name: 'create_document', title: 'Create a document', permissions: ['docs:write'], write: true,
405
+ description: "Creates a markdown document (or, with kind 'folder', a folder to group them) in a project's Documents section, at the end of its folder. It's a draft: nothing is exported until someone publishes a version of it in the UI.",
406
+ input: {
407
+ projectId,
408
+ title: zod_1.z.string().min(1),
409
+ content: zod_1.z.string().optional().describe('Its markdown'),
410
+ kind: zod_1.z.enum(['document', 'folder']).optional(),
411
+ parentId: zod_1.z.string().optional().describe("The folder it goes in (a folder's id); at the top if not given"),
412
+ },
413
+ projectOf: byProject,
414
+ call: ({ projectId, ...fields }) => ({ method: 'POST', path: `/projects/${enc(projectId)}/documents`, body: defined(fields) }),
415
+ pick: (data) => data?.document,
416
+ }),
417
+ tool({
418
+ name: 'update_document', title: 'Update a document', permissions: ['docs:write'], write: true,
419
+ description: "Saves a new version of a document: its whole markdown, its title, or both. `baseVersion` is the version it was read at (get_document): if someone saved another one since, nothing is saved and the answer says so; read it again and make the change on the new version. To change one part, update_document_section rewrites less.",
420
+ input: {
421
+ projectId, documentId: id('document'),
422
+ baseVersion: zod_1.z.number().int().min(1).describe('The version the change was made on'),
423
+ title: zod_1.z.string().min(1).optional(),
424
+ content: zod_1.z.string().optional().describe('The whole new markdown'),
425
+ },
426
+ projectOf: byProject,
427
+ call: ({ projectId, documentId, ...fields }) => ({ method: 'PATCH', path: `/projects/${enc(projectId)}/documents/${enc(documentId)}`, body: defined(fields) }),
428
+ pick: (data) => data?.document,
429
+ }),
430
+ tool({
431
+ name: 'update_document_section', title: "Update a document's section", permissions: ['docs:write'], write: true,
432
+ description: "Saves a new version of a document with only what's under one of its headings replaced (up to the next heading of the same level or above), the rest untouched. `heading` is the heading as written (\"## Architecture\") or its title alone. `baseVersion` is the version it was read at: if the document changed since, nothing is saved; read it again.",
433
+ input: {
434
+ projectId, documentId: id('document'),
435
+ baseVersion: zod_1.z.number().int().min(1).describe('The version the change was made on'),
436
+ heading: zod_1.z.string().min(1).describe('The section\'s heading, e.g. "## Architecture"'),
437
+ content: zod_1.z.string().describe("The section's new markdown, without its heading"),
438
+ },
439
+ projectOf: byProject,
440
+ call: ({ projectId, documentId, ...fields }) => ({ method: 'PATCH', path: `/projects/${enc(projectId)}/documents/${enc(documentId)}/sections`, body: fields }),
441
+ pick: (data) => data?.document,
442
+ }),
361
443
  // ── Projects ──
362
444
  tool({
363
445
  name: 'create_project', title: 'Create a project', permissions: ['projects:create'], write: true,
@@ -9,6 +9,7 @@ const crypto_1 = __importDefault(require("crypto"));
9
9
  const manager_1 = require("../models/manager");
10
10
  const auth_1 = require("../utils/auth");
11
11
  const auth_2 = require("../services/auth");
12
+ const workflowResources_1 = require("../services/workflowResources");
12
13
  // The node types that make a workflow's production section (as the UI groups them).
13
14
  const PRODUCTION_NODE_TYPES = new Set(['shotNode', 'assemblyNode', 'masterNode']);
14
15
  /** A Shot node, as the UI makes one dropped on the canvas without a position. */
@@ -32,14 +33,19 @@ async function ensureWorkflowNode(requirement, projectId, auth, api) {
32
33
  const project = await (0, manager_1.getModel)('projects').findOne({ _id: projectId });
33
34
  if (!project || !(0, auth_1.activeMemberOf)(project, auth.userId))
34
35
  return null;
35
- const schema = project.workflow?.nodeSchema ?? null;
36
- const nodes = Array.isArray(schema?.nodes) ? schema.nodes : [];
36
+ // The workflow as it's answered (brought to define the project, if it wasn't yet), and saved
37
+ // on that version: what changed since isn't undone.
38
+ const workflow = await (0, workflowResources_1.readWorkflow)(project);
39
+ const nodes = Array.isArray(workflow?.nodeSchema?.nodes) ? workflow.nodeSchema.nodes : [];
37
40
  if (nodes.some(node => PRODUCTION_NODE_TYPES.has(node?.type)))
38
41
  return null;
39
42
  const answer = await api({
40
43
  method: 'PATCH',
41
44
  path: `/projects/${encodeURIComponent(projectId)}/workflow`,
42
- body: { nodeSchema: { ...(schema ?? {}), nodes: [...nodes, (0, exports.newShotNode)()], edges: Array.isArray(schema?.edges) ? schema.edges : [] } },
45
+ body: {
46
+ nodeSchema: { nodes: [...nodes, (0, exports.newShotNode)()], edges: Array.isArray(workflow?.nodeSchema?.edges) ? workflow.nodeSchema.edges : [] },
47
+ baseUpdatedAt: new Date(workflow.updatedAt).toISOString(),
48
+ },
43
49
  });
44
50
  if (answer.status >= 200 && answer.status < 300)
45
51
  return null;
@@ -44,11 +44,15 @@ const worker_1 = require("./routes/worker");
44
44
  const workerCallback_1 = require("./routes/workerCallback");
45
45
  const workerJobs_1 = require("../services/workerJobs");
46
46
  const deliverable_1 = require("./routes/deliverable");
47
+ const document_1 = require("./routes/document");
48
+ const projectResources_1 = require("./routes/projectResources");
49
+ const documents_1 = require("../services/documents");
47
50
  function createStreamByRouter(config) {
48
51
  const router = express_1.default.Router();
49
52
  if (config.encrypt)
50
53
  (0, encryption_1.setEncryptionKey)(config.encrypt, config.encryptPreviousKeys);
51
54
  (0, jobStore_1.setJobStoreDatabase)(config);
55
+ (0, documents_1.setDocumentStoreDatabase)(config);
52
56
  // Establish DB connections asynchronously (Postgres/Mongo + schema migrations).
53
57
  // Until they're ready, HOLD incoming requests instead of letting them through —
54
58
  // route handlers resolve projects/exports (e.g. the /menu-list export) against a
@@ -149,5 +153,7 @@ function createStreamByRouter(config) {
149
153
  router.use((0, pipeline_1.pipelineRouter)(config));
150
154
  router.use((0, worker_1.workerRouter)({ ...config, adapter }));
151
155
  router.use((0, deliverable_1.deliverableRouter)({ ...config, adapter }));
156
+ router.use((0, document_1.documentRouter)(config));
157
+ router.use((0, projectResources_1.projectResourcesRouter)(config));
152
158
  return router;
153
159
  }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.connectionRouter = connectionRouter;
37
37
  const express_1 = require("express");
38
+ const workflowResources_1 = require("../../services/workflowResources");
38
39
  const manager_1 = require("../../models/manager");
39
40
  const auth_1 = require("../../utils/auth");
40
41
  const connection_1 = require("../../services/connection");
@@ -88,7 +89,8 @@ function connectionRouter(config) {
88
89
  ...(description !== undefined && { description }),
89
90
  ...(token !== undefined && { token }),
90
91
  });
91
- return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection) });
92
+ await (0, workflowResources_1.addToWorkflow)(projectId, 'api', connection);
93
+ return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection), connections: (0, sanitize_1.sanitizeConnections)((await Project.findOne({ _id: projectId }))?.connections) });
92
94
  }
93
95
  catch (err) {
94
96
  res.status(500).json({ message: 'Failed to add connection', details: err.message });
@@ -178,7 +180,8 @@ function connectionRouter(config) {
178
180
  return res.status(404).json({ message: 'Connection not found' });
179
181
  }
180
182
  await (0, connection_1.deleteConnection)(config, projectId, connectionId);
181
- return res.status(200).json({ message: 'Connection deleted successfully.' });
183
+ await (0, workflowResources_1.removeFromWorkflow)(projectId, 'api', connectionId);
184
+ return res.status(200).json({ message: 'Connection deleted successfully.', connections: (0, sanitize_1.sanitizeConnections)((await Project.findOne({ _id: projectId }))?.connections) });
182
185
  }
183
186
  catch (err) {
184
187
  res.status(500).json({ message: 'Failed to delete connection', details: err.message });