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.
- package/CHANGELOG.md +89 -0
- package/README.md +143 -8
- package/dist/adapters/database/connectionManager.js +33 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/mcp/tools.js +93 -11
- package/dist/mcp/workflowNodes.js +9 -3
- package/dist/middleware/createRouter.js +6 -0
- package/dist/middleware/routes/connection.js +5 -2
- package/dist/middleware/routes/dbConnection.js +29 -5
- package/dist/middleware/routes/document.d.ts +3 -0
- package/dist/middleware/routes/document.js +348 -0
- package/dist/middleware/routes/export.d.ts +28 -0
- package/dist/middleware/routes/export.js +8 -4
- package/dist/middleware/routes/pipeline.js +5 -2
- package/dist/middleware/routes/project.js +17 -12
- package/dist/middleware/routes/projectResources.d.ts +8 -0
- package/dist/middleware/routes/projectResources.js +51 -0
- package/dist/middleware/routes/rights.js +72 -52
- package/dist/middleware/routes/storage.js +35 -2
- package/dist/middleware/routes/storageConnection.js +38 -15
- package/dist/middleware/routes/userIntegration.js +42 -1
- package/dist/middleware/routes/workflow.js +120 -37
- package/dist/models/Model.js +12 -1
- package/dist/services/apiTokenPermissions.d.ts +1 -1
- package/dist/services/apiTokenPermissions.js +10 -0
- package/dist/services/auth.d.ts +2 -0
- package/dist/services/auth.js +17 -1
- package/dist/services/creditsSheets.d.ts +39 -0
- package/dist/services/creditsSheets.js +129 -0
- package/dist/services/dependents.d.ts +11 -0
- package/dist/services/dependents.js +81 -0
- package/dist/services/documents.d.ts +66 -0
- package/dist/services/documents.js +347 -0
- package/dist/services/executor.js +8 -0
- package/dist/services/oauthTokens.js +2 -2
- package/dist/services/production.d.ts +1 -1
- package/dist/services/production.js +2 -2
- package/dist/services/projectIntegration.d.ts +14 -0
- package/dist/services/projectIntegration.js +32 -0
- package/dist/services/rights.d.ts +44 -0
- package/dist/services/rights.js +123 -0
- package/dist/services/workflowResources.d.ts +90 -0
- package/dist/services/workflowResources.js +288 -0
- package/dist/types/index.d.ts +45 -0
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { NodeSchema, StreamByConfig } from '../types';
|
|
2
|
+
export type ResourceKind = 'db' | 'api' | 'storage' | 'service' | 'export' | 'pipeline';
|
|
3
|
+
type Row = 'input' | 'export' | 'pipeline';
|
|
4
|
+
interface NodeKind {
|
|
5
|
+
type: string;
|
|
6
|
+
field: string;
|
|
7
|
+
list: string;
|
|
8
|
+
row: Row;
|
|
9
|
+
subtitle: (resource: any) => string;
|
|
10
|
+
}
|
|
11
|
+
export declare const NODE_OF: Record<ResourceKind, NodeKind>;
|
|
12
|
+
export declare const CONNECT_PREFIX = "__connect__:";
|
|
13
|
+
declare const pendingOf: (value: unknown) => string | null;
|
|
14
|
+
declare const emptySchema: () => NodeSchema;
|
|
15
|
+
declare const schemaOf: (workflow: any) => NodeSchema;
|
|
16
|
+
/** The resource a node is bound to, if it's one of a resource's nodes and bound already. */
|
|
17
|
+
export declare function resourceOfNode(node: any): {
|
|
18
|
+
kind: ResourceKind;
|
|
19
|
+
id: string;
|
|
20
|
+
} | null;
|
|
21
|
+
/** The resources the workflow's nodes are bound to, by kind. */
|
|
22
|
+
export declare function referencedIn(schema: NodeSchema): Record<ResourceKind, Set<string>>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a node consumes its resource rather than being it: it holds a configuration of its own
|
|
25
|
+
* beside the resource's id (a data source reading one table, an Ingest importing one file, a
|
|
26
|
+
* service node set to an action). A workflow may have many beside the resource's own node, and
|
|
27
|
+
* they're never merged into it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function consumes(node: any): boolean;
|
|
30
|
+
/** Whether a node is its resource's own node (bound to it, and not one consuming it). */
|
|
31
|
+
export declare const isOwnNode: (node: any, kind: ResourceKind, id: string) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The schema with the resource's own node, if it has none yet (as the canvas wires one): nodes
|
|
34
|
+
* consuming it (an Ingest importing one of its files) aren't it, and removing them all mustn't
|
|
35
|
+
* remove it unasked.
|
|
36
|
+
*/
|
|
37
|
+
export declare function withResourceNode(schema: NodeSchema, kind: ResourceKind, resource: any): NodeSchema;
|
|
38
|
+
/** The schema without the resource's nodes, nor their edges. */
|
|
39
|
+
export declare function withoutResourceNodes(schema: NodeSchema, kind: ResourceKind, id: string): NodeSchema;
|
|
40
|
+
/** One node per resource: a second node bound to the same one merges into the first (its edges go to it). */
|
|
41
|
+
export declare function oneNodePerResource(schema: NodeSchema): NodeSchema;
|
|
42
|
+
/**
|
|
43
|
+
* A workflow made before it defined the project, brought to it once: the nodes once drawn from
|
|
44
|
+
* the project's lists bound by the id in their data (an API's had it only in its node id), the
|
|
45
|
+
* orchestrator there, a node for each resource it has without one, and one node per resource.
|
|
46
|
+
*/
|
|
47
|
+
export declare function migrateSchema(project: any, schema: NodeSchema): NodeSchema;
|
|
48
|
+
/** Saves the project's workflow (its single one, made if it has none) with the changes given. */
|
|
49
|
+
export declare function writeWorkflow(project: any, changes: Record<string, unknown>): Promise<any>;
|
|
50
|
+
/** The project's workflow as it's answered: brought once to define the project (see migrateSchema). */
|
|
51
|
+
export declare function readWorkflow(project: any): Promise<any>;
|
|
52
|
+
/** A resource added from its section (or by an agent): its node, in the workflow. */
|
|
53
|
+
export declare const addToWorkflow: (projectId: string, kind: ResourceKind, resource: any) => Promise<void>;
|
|
54
|
+
/** A resource removed from its section: its nodes, gone from the workflow. */
|
|
55
|
+
export declare const removeFromWorkflow: (projectId: string, kind: ResourceKind, id: string) => Promise<void>;
|
|
56
|
+
/** Rewrites ProjectInfo.pipelines from the pipelines collection, sorted by order. */
|
|
57
|
+
export declare function syncPipelineRefs(projectId: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Removes a resource from the project, only it (its node is the caller's: the workflow saved
|
|
60
|
+
* without it, or removeFromWorkflow). A built-in disconnected keeps its id, to come back as it was.
|
|
61
|
+
*/
|
|
62
|
+
export declare function removeResourceOf(project: any, kind: ResourceKind, id: string, config: StreamByConfig): Promise<void>;
|
|
63
|
+
/** A pipeline made in the project (its refs kept in sync), as its section makes one. */
|
|
64
|
+
export declare function createPipelineOf(projectId: string, name: string, userId: string): Promise<any>;
|
|
65
|
+
/** The project's resource lists as they're answered after a change. */
|
|
66
|
+
export declare function listsOf(projectId: string): Promise<{
|
|
67
|
+
dbConnections: (Omit<{
|
|
68
|
+
encryptedCredential?: string;
|
|
69
|
+
}, "encryptedCredential"> & {
|
|
70
|
+
hasCredential: boolean;
|
|
71
|
+
})[];
|
|
72
|
+
storageConnections: (Omit<{
|
|
73
|
+
encryptedCredential?: string;
|
|
74
|
+
}, "encryptedCredential"> & {
|
|
75
|
+
hasCredential: boolean;
|
|
76
|
+
})[];
|
|
77
|
+
integrationConnections: (Omit<{
|
|
78
|
+
encryptedCredential?: string;
|
|
79
|
+
}, "encryptedCredential"> & {
|
|
80
|
+
hasCredential: boolean;
|
|
81
|
+
})[];
|
|
82
|
+
connections: (Omit<{
|
|
83
|
+
encryptedCredential?: string;
|
|
84
|
+
}, "encryptedCredential"> & {
|
|
85
|
+
hasCredential: boolean;
|
|
86
|
+
})[];
|
|
87
|
+
exports: any[];
|
|
88
|
+
pipelines: any;
|
|
89
|
+
}>;
|
|
90
|
+
export { emptySchema, pendingOf, schemaOf };
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.schemaOf = exports.pendingOf = exports.emptySchema = exports.removeFromWorkflow = exports.addToWorkflow = exports.isOwnNode = exports.CONNECT_PREFIX = exports.NODE_OF = void 0;
|
|
7
|
+
exports.resourceOfNode = resourceOfNode;
|
|
8
|
+
exports.referencedIn = referencedIn;
|
|
9
|
+
exports.consumes = consumes;
|
|
10
|
+
exports.withResourceNode = withResourceNode;
|
|
11
|
+
exports.withoutResourceNodes = withoutResourceNodes;
|
|
12
|
+
exports.oneNodePerResource = oneNodePerResource;
|
|
13
|
+
exports.migrateSchema = migrateSchema;
|
|
14
|
+
exports.writeWorkflow = writeWorkflow;
|
|
15
|
+
exports.readWorkflow = readWorkflow;
|
|
16
|
+
exports.syncPipelineRefs = syncPipelineRefs;
|
|
17
|
+
exports.removeResourceOf = removeResourceOf;
|
|
18
|
+
exports.createPipelineOf = createPipelineOf;
|
|
19
|
+
exports.listsOf = listsOf;
|
|
20
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
21
|
+
const mongodb_1 = require("mongodb");
|
|
22
|
+
const manager_1 = require("../models/manager");
|
|
23
|
+
const nosqlDb_1 = require("../utils/nosqlDb");
|
|
24
|
+
const connection_1 = require("./connection");
|
|
25
|
+
const export_1 = require("./export");
|
|
26
|
+
const deliverables_1 = require("./deliverables");
|
|
27
|
+
const sanitize_1 = require("../utils/sanitize");
|
|
28
|
+
exports.NODE_OF = {
|
|
29
|
+
db: { type: 'dataSourceNode', field: 'connectionId', list: 'dbConnections', row: 'input', subtitle: r => r.dbType },
|
|
30
|
+
api: { type: 'connectionNode', field: 'connectionId', list: 'connections', row: 'input', subtitle: r => r.method ?? 'GET' },
|
|
31
|
+
storage: { type: 'ingestNode', field: 'storageConnectionId', list: 'storageConnections', row: 'input', subtitle: r => r.type },
|
|
32
|
+
service: { type: 'integrationConnectionNode', field: 'integrationConnectionId', list: 'integrationConnections', row: 'input', subtitle: r => r.provider },
|
|
33
|
+
export: { type: 'exportNode', field: 'exportId', list: 'exports', row: 'export', subtitle: r => r.method || 'GET' },
|
|
34
|
+
pipeline: { type: 'pipelineRefNode', field: 'pipelineId', list: 'pipelines', row: 'pipeline', subtitle: () => 'Pipeline' },
|
|
35
|
+
};
|
|
36
|
+
const KINDS = Object.keys(exports.NODE_OF);
|
|
37
|
+
const KIND_OF_TYPE = new Map(KINDS.map(kind => [exports.NODE_OF[kind].type, kind]));
|
|
38
|
+
// A node set to an integration the project doesn't have yet: connected when the workflow is saved.
|
|
39
|
+
exports.CONNECT_PREFIX = '__connect__:';
|
|
40
|
+
const pendingOf = (value) => (typeof value === 'string' && value.startsWith(exports.CONNECT_PREFIX) ? value.slice(exports.CONNECT_PREFIX.length) : null);
|
|
41
|
+
exports.pendingOf = pendingOf;
|
|
42
|
+
const ORCHESTRATOR = 'streamby';
|
|
43
|
+
const EDGE_STYLE = { stroke: '#38b6ff', strokeWidth: 1.5 };
|
|
44
|
+
const emptySchema = () => ({ nodes: [], edges: [] });
|
|
45
|
+
exports.emptySchema = emptySchema;
|
|
46
|
+
const schemaOf = (workflow) => ({
|
|
47
|
+
nodes: Array.isArray(workflow?.nodeSchema?.nodes) ? workflow.nodeSchema.nodes : [],
|
|
48
|
+
edges: Array.isArray(workflow?.nodeSchema?.edges) ? workflow.nodeSchema.edges : [],
|
|
49
|
+
});
|
|
50
|
+
exports.schemaOf = schemaOf;
|
|
51
|
+
/** The resource a node is bound to, if it's one of a resource's nodes and bound already. */
|
|
52
|
+
function resourceOfNode(node) {
|
|
53
|
+
const kind = KIND_OF_TYPE.get(node?.type);
|
|
54
|
+
const id = kind ? node?.data?.[exports.NODE_OF[kind].field] : null;
|
|
55
|
+
return kind && typeof id === 'string' && id && !pendingOf(id) ? { kind, id } : null;
|
|
56
|
+
}
|
|
57
|
+
/** The resources the workflow's nodes are bound to, by kind. */
|
|
58
|
+
function referencedIn(schema) {
|
|
59
|
+
const refs = Object.fromEntries(KINDS.map(kind => [kind, new Set()]));
|
|
60
|
+
for (const node of schema.nodes) {
|
|
61
|
+
const ref = resourceOfNode(node);
|
|
62
|
+
if (ref)
|
|
63
|
+
refs[ref.kind].add(ref.id);
|
|
64
|
+
}
|
|
65
|
+
return refs;
|
|
66
|
+
}
|
|
67
|
+
// What a node's data holds only to be shown (its name, how it's drawn), not what it does.
|
|
68
|
+
const PRESENTATION = new Set(['label', 'subtitle', 'icon', 'bgColor', 'iconColor', 'description']);
|
|
69
|
+
/**
|
|
70
|
+
* Whether a node consumes its resource rather than being it: it holds a configuration of its own
|
|
71
|
+
* beside the resource's id (a data source reading one table, an Ingest importing one file, a
|
|
72
|
+
* service node set to an action). A workflow may have many beside the resource's own node, and
|
|
73
|
+
* they're never merged into it.
|
|
74
|
+
*/
|
|
75
|
+
function consumes(node) {
|
|
76
|
+
const kind = KIND_OF_TYPE.get(node?.type);
|
|
77
|
+
if (!kind)
|
|
78
|
+
return false;
|
|
79
|
+
return Object.entries(node?.data ?? {}).some(([key, value]) => key !== exports.NODE_OF[kind].field && !PRESENTATION.has(key) && value !== '' && value !== null && value !== undefined && !(typeof value === 'object' && !Object.keys(value).length));
|
|
80
|
+
}
|
|
81
|
+
/** Whether a node is its resource's own node (bound to it, and not one consuming it). */
|
|
82
|
+
const isOwnNode = (node, kind, id) => {
|
|
83
|
+
const ref = resourceOfNode(node);
|
|
84
|
+
return ref?.kind === kind && ref.id === id && !consumes(node);
|
|
85
|
+
};
|
|
86
|
+
exports.isOwnNode = isOwnNode;
|
|
87
|
+
// Where a new resource's node goes, as the canvas lays them out around the orchestrator: the
|
|
88
|
+
// inputs above it, the exports below, the pipelines on its left.
|
|
89
|
+
function placeFor(schema, row) {
|
|
90
|
+
const orchestrator = schema.nodes.find(n => n.id === ORCHESTRATOR)?.position ?? { x: 350, y: 20 };
|
|
91
|
+
const near = (a, b) => Math.abs(a - b) < 60;
|
|
92
|
+
if (row === 'pipeline') {
|
|
93
|
+
const x = orchestrator.x - 350;
|
|
94
|
+
const ys = schema.nodes.filter(n => n.position && near(n.position.x, x)).map(n => n.position.y);
|
|
95
|
+
return { x, y: ys.length ? Math.max(...ys) + 220 : orchestrator.y };
|
|
96
|
+
}
|
|
97
|
+
const y = row === 'input' ? orchestrator.y - 180 : orchestrator.y + 200;
|
|
98
|
+
const xs = schema.nodes.filter(n => n.position && near(n.position.y, y)).map(n => n.position.x);
|
|
99
|
+
return { x: xs.length ? Math.max(...xs) + 220 : orchestrator.x, y };
|
|
100
|
+
}
|
|
101
|
+
const orchestratorNode = () => ({ id: ORCHESTRATOR, type: 'orchestratorNode', position: { x: 350, y: 20 }, data: { label: 'StreamBy', subtitle: 'Orchestrator' } });
|
|
102
|
+
/**
|
|
103
|
+
* The schema with the resource's own node, if it has none yet (as the canvas wires one): nodes
|
|
104
|
+
* consuming it (an Ingest importing one of its files) aren't it, and removing them all mustn't
|
|
105
|
+
* remove it unasked.
|
|
106
|
+
*/
|
|
107
|
+
function withResourceNode(schema, kind, resource) {
|
|
108
|
+
const id = String(resource.id);
|
|
109
|
+
if (schema.nodes.some(n => (0, exports.isOwnNode)(n, kind, id)))
|
|
110
|
+
return schema;
|
|
111
|
+
const def = exports.NODE_OF[kind];
|
|
112
|
+
const base = schema.nodes.some(n => n.id === ORCHESTRATOR) ? schema : { ...schema, nodes: [orchestratorNode(), ...schema.nodes] };
|
|
113
|
+
const nodeId = `${kind}-${id}-${crypto_1.default.randomUUID().slice(0, 8)}`;
|
|
114
|
+
const node = { id: nodeId, type: def.type, position: placeFor(base, def.row), data: { label: resource.name ?? id, subtitle: def.subtitle(resource), [def.field]: id } };
|
|
115
|
+
const edge = def.row === 'input'
|
|
116
|
+
? { id: `e-${nodeId}`, source: nodeId, sourceHandle: 'out-stream', target: ORCHESTRATOR, targetHandle: 'in-top', animated: false, style: EDGE_STYLE }
|
|
117
|
+
: def.row === 'export'
|
|
118
|
+
? { id: `e-${nodeId}`, source: ORCHESTRATOR, sourceHandle: 'out-bottom', target: nodeId, targetHandle: 'in-orchestrator-bottom', animated: true, style: EDGE_STYLE }
|
|
119
|
+
: { id: `e-${nodeId}`, source: ORCHESTRATOR, sourceHandle: 'out-pipeline', target: nodeId, targetHandle: 'in-orchestrator', animated: true, style: EDGE_STYLE };
|
|
120
|
+
return { nodes: [...base.nodes, node], edges: [...base.edges, edge] };
|
|
121
|
+
}
|
|
122
|
+
/** The schema without the resource's nodes, nor their edges. */
|
|
123
|
+
function withoutResourceNodes(schema, kind, id) {
|
|
124
|
+
const gone = new Set(schema.nodes.filter(n => { const ref = resourceOfNode(n); return ref?.kind === kind && ref.id === id; }).map(n => n.id));
|
|
125
|
+
return { nodes: schema.nodes.filter(n => !gone.has(n.id)), edges: schema.edges.filter(e => !gone.has(e.source) && !gone.has(e.target)) };
|
|
126
|
+
}
|
|
127
|
+
/** One node per resource: a second node bound to the same one merges into the first (its edges go to it). */
|
|
128
|
+
function oneNodePerResource(schema) {
|
|
129
|
+
const first = new Map();
|
|
130
|
+
const merged = new Map();
|
|
131
|
+
const nodes = schema.nodes.filter(node => {
|
|
132
|
+
const ref = resourceOfNode(node);
|
|
133
|
+
if (!ref || consumes(node))
|
|
134
|
+
return true;
|
|
135
|
+
const key = `${ref.kind}:${ref.id}`;
|
|
136
|
+
const kept = first.get(key);
|
|
137
|
+
if (!kept) {
|
|
138
|
+
first.set(key, node.id);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
merged.set(node.id, kept);
|
|
142
|
+
return false;
|
|
143
|
+
});
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
const edges = schema.edges
|
|
146
|
+
.map(e => ({ ...e, source: merged.get(e.source) ?? e.source, target: merged.get(e.target) ?? e.target }))
|
|
147
|
+
.filter(e => {
|
|
148
|
+
const key = `${e.source}|${e.sourceHandle ?? ''}|${e.target}|${e.targetHandle ?? ''}`;
|
|
149
|
+
if (e.source === e.target || seen.has(key))
|
|
150
|
+
return false;
|
|
151
|
+
seen.add(key);
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
return { nodes, edges };
|
|
155
|
+
}
|
|
156
|
+
const LEGACY_PREFIX = [['db-', 'db'], ['api-', 'api'], ['storage-', 'storage'], ['svc-', 'service'], ['export-', 'export'], ['pipeline-', 'pipeline']];
|
|
157
|
+
/**
|
|
158
|
+
* A workflow made before it defined the project, brought to it once: the nodes once drawn from
|
|
159
|
+
* the project's lists bound by the id in their data (an API's had it only in its node id), the
|
|
160
|
+
* orchestrator there, a node for each resource it has without one, and one node per resource.
|
|
161
|
+
*/
|
|
162
|
+
function migrateSchema(project, schema) {
|
|
163
|
+
let nodes = schema.nodes.map(node => {
|
|
164
|
+
const legacy = LEGACY_PREFIX.find(([prefix, kind]) => typeof node?.id === 'string' && node.id.startsWith(prefix) && exports.NODE_OF[kind].type === node.type);
|
|
165
|
+
if (!legacy)
|
|
166
|
+
return node;
|
|
167
|
+
const def = exports.NODE_OF[legacy[1]];
|
|
168
|
+
return node.data?.[def.field] ? node : { ...node, data: { ...node.data, [def.field]: node.id.slice(legacy[0].length) } };
|
|
169
|
+
});
|
|
170
|
+
if (!nodes.some(n => n.id === ORCHESTRATOR))
|
|
171
|
+
nodes = [orchestratorNode(), ...nodes];
|
|
172
|
+
let migrated = { nodes, edges: schema.edges };
|
|
173
|
+
for (const kind of KINDS) {
|
|
174
|
+
for (const resource of (project[exports.NODE_OF[kind].list] ?? []))
|
|
175
|
+
migrated = withResourceNode(migrated, kind, resource);
|
|
176
|
+
}
|
|
177
|
+
return oneNodePerResource(migrated);
|
|
178
|
+
}
|
|
179
|
+
// ── Reading and writing a project's workflow ──
|
|
180
|
+
/** Saves the project's workflow (its single one, made if it has none) with the changes given. */
|
|
181
|
+
async function writeWorkflow(project, changes) {
|
|
182
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
183
|
+
const projectId = String(project._id ?? project.id);
|
|
184
|
+
// Each save a later version than the one before, even within the same millisecond: a save is
|
|
185
|
+
// made on the version it was read at (baseUpdatedAt), so two can't share one.
|
|
186
|
+
const before = project.workflow?.updatedAt ? new Date(project.workflow.updatedAt).getTime() : 0;
|
|
187
|
+
const now = new Date(Math.max(Date.now(), before + 1));
|
|
188
|
+
const base = project.workflow ?? { id: crypto_1.default.randomUUID(), name: 'Workflow', description: '', status: 'draft', projectId, nodeSchema: null, createdAt: now };
|
|
189
|
+
const updated = { ...base, ...changes, id: base.id, projectId, updatedAt: now };
|
|
190
|
+
if (project.dbType === 'sql') {
|
|
191
|
+
await (0, manager_1.getModel)('projects', 'sql').update({ _id: projectId }, { workflow: updated });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await Project.update({ _id: new mongodb_1.ObjectId(projectId) }, { $set: { workflow: updated } });
|
|
195
|
+
}
|
|
196
|
+
return updated;
|
|
197
|
+
}
|
|
198
|
+
/** The project's workflow as it's answered: brought once to define the project (see migrateSchema). */
|
|
199
|
+
async function readWorkflow(project) {
|
|
200
|
+
if (project.workflow?.resourceNodesAt)
|
|
201
|
+
return project.workflow;
|
|
202
|
+
const nodeSchema = migrateSchema(project, schemaOf(project.workflow));
|
|
203
|
+
return writeWorkflow(project, { nodeSchema, resourceNodesAt: new Date() });
|
|
204
|
+
}
|
|
205
|
+
// A change to the workflow saved, made on it as it is now (read again: another one may have
|
|
206
|
+
// been saved since the project was read).
|
|
207
|
+
async function changeSchema(projectId, change) {
|
|
208
|
+
const project = await (0, manager_1.getModel)('projects').findOne({ _id: projectId });
|
|
209
|
+
if (!project)
|
|
210
|
+
return;
|
|
211
|
+
const current = project.workflow?.resourceNodesAt ? schemaOf(project.workflow) : migrateSchema(project, schemaOf(project.workflow));
|
|
212
|
+
await writeWorkflow(project, { nodeSchema: change(current), resourceNodesAt: project.workflow?.resourceNodesAt ?? new Date() });
|
|
213
|
+
}
|
|
214
|
+
/** A resource added from its section (or by an agent): its node, in the workflow. */
|
|
215
|
+
const addToWorkflow = (projectId, kind, resource) => changeSchema(projectId, schema => withResourceNode(schema, kind, resource));
|
|
216
|
+
exports.addToWorkflow = addToWorkflow;
|
|
217
|
+
/** A resource removed from its section: its nodes, gone from the workflow. */
|
|
218
|
+
const removeFromWorkflow = (projectId, kind, id) => changeSchema(projectId, schema => withoutResourceNodes(schema, kind, id));
|
|
219
|
+
exports.removeFromWorkflow = removeFromWorkflow;
|
|
220
|
+
// ── Removing a resource ──
|
|
221
|
+
/** Rewrites ProjectInfo.pipelines from the pipelines collection, sorted by order. */
|
|
222
|
+
async function syncPipelineRefs(projectId) {
|
|
223
|
+
const db = (0, nosqlDb_1.nosqlDb)();
|
|
224
|
+
if (!db)
|
|
225
|
+
return;
|
|
226
|
+
const all = await db.collection('pipelines').find({ projectId }).project({ _id: 0, id: 1, name: 1, order: 1 }).sort({ order: 1, createdAt: 1 }).toArray();
|
|
227
|
+
await (0, manager_1.getModel)('projects').update({ _id: projectId }, { pipelines: all.map((p) => ({ id: p.id, name: p.name, order: p.order })) });
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Removes a resource from the project, only it (its node is the caller's: the workflow saved
|
|
231
|
+
* without it, or removeFromWorkflow). A built-in disconnected keeps its id, to come back as it was.
|
|
232
|
+
*/
|
|
233
|
+
async function removeResourceOf(project, kind, id, config) {
|
|
234
|
+
const Project = (0, manager_1.getModel)('projects');
|
|
235
|
+
const projectId = String(project._id ?? project.id);
|
|
236
|
+
if (kind === 'db' || kind === 'storage' || kind === 'service') {
|
|
237
|
+
const list = exports.NODE_OF[kind].list;
|
|
238
|
+
const conn = (project[list] ?? []).find((c) => c.id === id);
|
|
239
|
+
if (!conn)
|
|
240
|
+
return;
|
|
241
|
+
await Project.update({ _id: projectId }, { $pull: { [list]: { id } } });
|
|
242
|
+
if (conn.source === 'builtin' && conn.integrationId && kind !== 'service') {
|
|
243
|
+
await Project.update({ _id: projectId }, { $pull: { disconnectedBuiltins: { integrationId: conn.integrationId } } });
|
|
244
|
+
await Project.update({ _id: projectId }, { $push: { disconnectedBuiltins: { kind, integrationId: conn.integrationId, connectionId: conn.id } } });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (kind === 'api') {
|
|
248
|
+
if ((project.connections ?? []).some((c) => c.id === id))
|
|
249
|
+
await (0, connection_1.deleteConnection)(config, projectId, id);
|
|
250
|
+
}
|
|
251
|
+
else if (kind === 'export') {
|
|
252
|
+
if ((project.exports ?? []).some((e) => String(e.id) === id)) {
|
|
253
|
+
await (0, export_1.deleteExport)(config, projectId, id, project.dbType);
|
|
254
|
+
await (0, deliverables_1.deleteDeliverablesOf)(projectId, { exportId: id });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
await (0, nosqlDb_1.nosqlDb)()?.collection('pipelines').deleteOne({ id, projectId });
|
|
259
|
+
await syncPipelineRefs(projectId);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** A pipeline made in the project (its refs kept in sync), as its section makes one. */
|
|
263
|
+
async function createPipelineOf(projectId, name, userId) {
|
|
264
|
+
const db = (0, nosqlDb_1.nosqlDb)();
|
|
265
|
+
if (!db)
|
|
266
|
+
throw new Error('Database not available');
|
|
267
|
+
const project = await (0, manager_1.getModel)('projects').findOne({ _id: projectId });
|
|
268
|
+
const now = new Date();
|
|
269
|
+
const pipeline = {
|
|
270
|
+
id: crypto_1.default.randomUUID(), projectId, name, description: null, order: project?.pipelines?.length ?? 0,
|
|
271
|
+
nodeSchema: null, createdBy: userId, createdAt: now, updatedAt: now,
|
|
272
|
+
};
|
|
273
|
+
await db.collection('pipelines').insertOne({ ...pipeline });
|
|
274
|
+
await syncPipelineRefs(projectId);
|
|
275
|
+
return pipeline;
|
|
276
|
+
}
|
|
277
|
+
/** The project's resource lists as they're answered after a change. */
|
|
278
|
+
async function listsOf(projectId) {
|
|
279
|
+
const project = await (0, manager_1.getModel)('projects').findOne({ _id: projectId });
|
|
280
|
+
return {
|
|
281
|
+
dbConnections: (0, sanitize_1.sanitizeConnections)(project?.dbConnections),
|
|
282
|
+
storageConnections: (0, sanitize_1.sanitizeConnections)(project?.storageConnections),
|
|
283
|
+
integrationConnections: (0, sanitize_1.sanitizeConnections)(project?.integrationConnections),
|
|
284
|
+
connections: (0, sanitize_1.sanitizeConnections)(project?.connections),
|
|
285
|
+
exports: (0, sanitize_1.exportSummaries)(project?.exports),
|
|
286
|
+
pipelines: project?.pipelines ?? [],
|
|
287
|
+
};
|
|
288
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -82,6 +82,11 @@ export interface ProjectInfo {
|
|
|
82
82
|
integrationConnections?: IntegrationConnection[];
|
|
83
83
|
pipelines?: PipelineRef[];
|
|
84
84
|
builtinBackfilledAt?: Date;
|
|
85
|
+
disconnectedBuiltins?: {
|
|
86
|
+
kind: 'db' | 'storage';
|
|
87
|
+
integrationId: string;
|
|
88
|
+
connectionId: string;
|
|
89
|
+
}[];
|
|
85
90
|
workflow?: Workflow;
|
|
86
91
|
}
|
|
87
92
|
export type NodePersistence = 'database' | 'storage' | 'both' | 'none';
|
|
@@ -529,3 +534,43 @@ export interface AssetVersionRecord {
|
|
|
529
534
|
changeNote?: string;
|
|
530
535
|
createdAt: Date;
|
|
531
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Who saved a document's version: a user in the UI, an API token, or an app the user let act
|
|
539
|
+
* for them (OAuth, e.g. Claude in claude.ai).
|
|
540
|
+
*/
|
|
541
|
+
export interface DocumentAuthor {
|
|
542
|
+
userId: string;
|
|
543
|
+
username: string;
|
|
544
|
+
via: 'session' | 'apiToken' | 'oauth';
|
|
545
|
+
tokenId?: string;
|
|
546
|
+
clientId?: string;
|
|
547
|
+
clientName?: string;
|
|
548
|
+
}
|
|
549
|
+
export type DocumentKind = 'document' | 'folder' | 'credits';
|
|
550
|
+
/** A project's document as it is now (a folder only groups others: it has no content). */
|
|
551
|
+
export interface ProjectDocument {
|
|
552
|
+
id: string;
|
|
553
|
+
projectId: string;
|
|
554
|
+
kind: DocumentKind;
|
|
555
|
+
title: string;
|
|
556
|
+
parentId: string | null;
|
|
557
|
+
position: number;
|
|
558
|
+
content: string;
|
|
559
|
+
version: number;
|
|
560
|
+
exportable: boolean;
|
|
561
|
+
publishedVersion: number | null;
|
|
562
|
+
createdAt: Date;
|
|
563
|
+
createdBy: DocumentAuthor;
|
|
564
|
+
updatedAt: Date;
|
|
565
|
+
updatedBy: DocumentAuthor;
|
|
566
|
+
}
|
|
567
|
+
/** One save of a document: its title and content then, who saved it and when. */
|
|
568
|
+
export interface DocumentVersion {
|
|
569
|
+
documentId: string;
|
|
570
|
+
projectId: string;
|
|
571
|
+
version: number;
|
|
572
|
+
title: string;
|
|
573
|
+
content: string;
|
|
574
|
+
author: DocumentAuthor;
|
|
575
|
+
createdAt: Date;
|
|
576
|
+
}
|