streamby-core 0.29.12 → 0.32.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/dist/adapters/database/connectionManager.js +7 -4
- package/dist/middleware/createRouter.js +0 -2
- package/dist/middleware/routes/ai.js +1 -1
- package/dist/middleware/routes/connection.js +35 -50
- package/dist/middleware/routes/dbConnection.js +60 -222
- package/dist/middleware/routes/distribution.js +5 -4
- package/dist/middleware/routes/export.js +10 -17
- package/dist/middleware/routes/project.js +1 -1
- package/dist/middleware/routes/renderFarm.js +5 -4
- package/dist/middleware/routes/storage.js +2 -43
- package/dist/middleware/routes/storageConnection.js +15 -12
- package/dist/services/backfillBuiltinConnections.js +0 -2
- package/dist/services/connection.d.ts +18 -0
- package/dist/services/connection.js +78 -0
- package/dist/services/connectionResolver.d.ts +19 -0
- package/dist/services/connectionResolver.js +127 -0
- package/dist/services/executor.d.ts +5 -0
- package/dist/services/executor.js +53 -27
- package/dist/services/projectIntegration.js +4 -4
- package/dist/types/index.d.ts +20 -14
- package/dist/types/nodePersistence.d.ts +3 -0
- package/dist/types/nodePersistence.js +15 -0
- package/dist/utils/sanitize.d.ts +10 -0
- package/dist/utils/sanitize.js +16 -1
- package/package.json +1 -1
|
@@ -6,6 +6,8 @@ const mongodb_1 = require("mongodb");
|
|
|
6
6
|
const manager_1 = require("../../models/manager");
|
|
7
7
|
const auth_1 = require("../../utils/auth");
|
|
8
8
|
const builtinAccess_1 = require("../../utils/builtinAccess");
|
|
9
|
+
const sanitize_1 = require("../../utils/sanitize");
|
|
10
|
+
const encryption_1 = require("../../utils/encryption");
|
|
9
11
|
const VALID_STORAGE_TYPES = ['s3', 'gcs', 'r2', 'azure'];
|
|
10
12
|
function storageConnectionRouter(config) {
|
|
11
13
|
const router = (0, express_1.Router)();
|
|
@@ -24,9 +26,9 @@ function storageConnectionRouter(config) {
|
|
|
24
26
|
// "validate on every use" (TCORE-69). BYOC (source: 'integration') is always available.
|
|
25
27
|
const data = await Promise.all(connections.map(async (conn) => {
|
|
26
28
|
if (conn.source !== 'builtin')
|
|
27
|
-
return conn;
|
|
29
|
+
return (0, sanitize_1.sanitizeConnection)(conn);
|
|
28
30
|
const available = await (0, builtinAccess_1.assertBuiltinAccess)(auth, conn.integrationId ?? conn.id, config, 'storage');
|
|
29
|
-
return { ...conn, isBuiltin: true, available };
|
|
31
|
+
return (0, sanitize_1.sanitizeConnection)({ ...conn, isBuiltin: true, available });
|
|
30
32
|
}));
|
|
31
33
|
return res.status(200).json({ data });
|
|
32
34
|
}
|
|
@@ -41,15 +43,15 @@ function storageConnectionRouter(config) {
|
|
|
41
43
|
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
42
44
|
return res.status(403).json({ message: 'Permission denied' });
|
|
43
45
|
}
|
|
44
|
-
const { name, type,
|
|
46
|
+
const { name, type, credential, integrationId, description } = req.body;
|
|
45
47
|
if (!name || !type) {
|
|
46
48
|
return res.status(400).json({ message: 'name and type are required' });
|
|
47
49
|
}
|
|
48
|
-
if (!
|
|
49
|
-
return res.status(400).json({ message: 'Either
|
|
50
|
+
if (!credential && !integrationId) {
|
|
51
|
+
return res.status(400).json({ message: 'Either credential or integrationId is required' });
|
|
50
52
|
}
|
|
51
|
-
if (
|
|
52
|
-
return res.status(400).json({ message: 'Provide only one of
|
|
53
|
+
if (credential && integrationId) {
|
|
54
|
+
return res.status(400).json({ message: 'Provide only one of credential or integrationId' });
|
|
53
55
|
}
|
|
54
56
|
if (!VALID_STORAGE_TYPES.includes(type)) {
|
|
55
57
|
return res.status(400).json({ message: `type must be one of: ${VALID_STORAGE_TYPES.join(', ')}` });
|
|
@@ -59,6 +61,7 @@ function storageConnectionRouter(config) {
|
|
|
59
61
|
return res.status(404).json({ message: 'Project not found' });
|
|
60
62
|
if (!(0, auth_1.isProjectMember)(project, auth.userId))
|
|
61
63
|
return res.status(403).json({ message: 'Unauthorized project access' });
|
|
64
|
+
let encryptedCredential;
|
|
62
65
|
if (integrationId) {
|
|
63
66
|
const UserIntegrationModel = (0, manager_1.getModel)('user_integrations');
|
|
64
67
|
const integration = await UserIntegrationModel.findOne({ id: integrationId, userId: auth.userId });
|
|
@@ -68,23 +71,23 @@ function storageConnectionRouter(config) {
|
|
|
68
71
|
return res.status(400).json({ message: 'Integration is not a storage integration' });
|
|
69
72
|
}
|
|
70
73
|
else {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
75
|
+
return res.status(500).json({ message: 'Encryption key is not set' });
|
|
76
|
+
encryptedCredential = (0, encryption_1.encrypt)(credential);
|
|
74
77
|
}
|
|
75
78
|
const connection = {
|
|
76
79
|
id: new mongodb_1.ObjectId().toHexString(),
|
|
77
80
|
name,
|
|
78
81
|
type,
|
|
79
|
-
credentialId: credentialId ?? '',
|
|
80
82
|
projectId: req.params.id,
|
|
81
83
|
createdAt: new Date(),
|
|
82
84
|
source: integrationId ? 'integration' : 'manual',
|
|
83
85
|
...(integrationId && { integrationId }),
|
|
86
|
+
...(encryptedCredential !== undefined && { encryptedCredential }),
|
|
84
87
|
...(description !== undefined && { description }),
|
|
85
88
|
};
|
|
86
89
|
await Project.update({ _id: req.params.id }, { $push: { storageConnections: connection } });
|
|
87
|
-
return res.status(201).json({ data: connection });
|
|
90
|
+
return res.status(201).json({ data: (0, sanitize_1.sanitizeConnection)(connection) });
|
|
88
91
|
}
|
|
89
92
|
catch (err) {
|
|
90
93
|
res.status(500).json({ message: 'Failed to add storage connection', details: err.message });
|
|
@@ -29,7 +29,6 @@ async function backfillBuiltinConnections(config) {
|
|
|
29
29
|
id: crypto_1.default.randomUUID(),
|
|
30
30
|
name: builtinAccess_1.BUILTIN_DB_DISPLAY[db.type] ?? db.type,
|
|
31
31
|
dbType: db.type === 'sql' ? 'postgresql' : 'mongodb',
|
|
32
|
-
credentialId: '',
|
|
33
32
|
projectId: project.id,
|
|
34
33
|
createdAt: now,
|
|
35
34
|
isBuiltin: true,
|
|
@@ -40,7 +39,6 @@ async function backfillBuiltinConnections(config) {
|
|
|
40
39
|
id: crypto_1.default.randomUUID(),
|
|
41
40
|
name: builtinAccess_1.BUILTIN_STORAGE_DISPLAY[provider.type] ?? provider.type,
|
|
42
41
|
type: provider.type,
|
|
43
|
-
credentialId: '',
|
|
44
42
|
projectId: project.id,
|
|
45
43
|
createdAt: now,
|
|
46
44
|
isBuiltin: true,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StreamByConfig, Connection, ConnectionMethod } from '../types';
|
|
2
|
+
export declare function addConnection(config: StreamByConfig, projectId: string, data: {
|
|
3
|
+
name: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
method: ConnectionMethod;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
}): Promise<Connection>;
|
|
10
|
+
export declare function updateConnection(config: StreamByConfig, projectId: string, connectionId: string, data: {
|
|
11
|
+
name?: string;
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
method?: ConnectionMethod;
|
|
14
|
+
prefix?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
}): Promise<Connection>;
|
|
18
|
+
export declare function deleteConnection(config: StreamByConfig, projectId: string, connectionId: string): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addConnection = addConnection;
|
|
4
|
+
exports.updateConnection = updateConnection;
|
|
5
|
+
exports.deleteConnection = deleteConnection;
|
|
6
|
+
const mongodb_1 = require("mongodb");
|
|
7
|
+
const manager_1 = require("../models/manager");
|
|
8
|
+
const encryption_1 = require("../utils/encryption");
|
|
9
|
+
async function addConnection(config, projectId, data) {
|
|
10
|
+
const ProjectModel = (0, manager_1.getModel)('projects');
|
|
11
|
+
const project = await ProjectModel.findOne({ _id: projectId });
|
|
12
|
+
if (!project) {
|
|
13
|
+
throw new Error('Project not found.');
|
|
14
|
+
}
|
|
15
|
+
let encryptedCredential;
|
|
16
|
+
if (data.token) {
|
|
17
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
18
|
+
throw new Error('Encryption key is not set. Cannot encrypt credential.');
|
|
19
|
+
encryptedCredential = (0, encryption_1.encrypt)(data.token);
|
|
20
|
+
}
|
|
21
|
+
const connection = {
|
|
22
|
+
id: new mongodb_1.ObjectId().toHexString(),
|
|
23
|
+
name: data.name,
|
|
24
|
+
prefix: data.prefix,
|
|
25
|
+
apiUrl: data.apiUrl,
|
|
26
|
+
method: data.method,
|
|
27
|
+
projectId,
|
|
28
|
+
createdAt: new Date(),
|
|
29
|
+
...(data.description !== undefined && { description: data.description }),
|
|
30
|
+
...(encryptedCredential !== undefined && { encryptedCredential }),
|
|
31
|
+
};
|
|
32
|
+
const updatedProject = await ProjectModel.update({ _id: projectId }, { $push: { connections: connection } });
|
|
33
|
+
if (!updatedProject) {
|
|
34
|
+
throw new Error('Failed to add connection to project.');
|
|
35
|
+
}
|
|
36
|
+
return connection;
|
|
37
|
+
}
|
|
38
|
+
async function updateConnection(config, projectId, connectionId, data) {
|
|
39
|
+
const ProjectModel = (0, manager_1.getModel)('projects');
|
|
40
|
+
const project = await ProjectModel.findOne({ _id: projectId });
|
|
41
|
+
if (!project)
|
|
42
|
+
throw new Error('Project not found.');
|
|
43
|
+
const connections = project.connections ?? [];
|
|
44
|
+
const index = connections.findIndex((c) => c.id === connectionId);
|
|
45
|
+
if (index === -1)
|
|
46
|
+
throw new Error('Connection not found.');
|
|
47
|
+
let encryptedCredential;
|
|
48
|
+
if (data.token) {
|
|
49
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
50
|
+
throw new Error('Encryption key is not set. Cannot encrypt credential.');
|
|
51
|
+
encryptedCredential = (0, encryption_1.encrypt)(data.token);
|
|
52
|
+
}
|
|
53
|
+
const updated = {
|
|
54
|
+
...connections[index],
|
|
55
|
+
...(data.name !== undefined && { name: data.name }),
|
|
56
|
+
...(data.apiUrl !== undefined && { apiUrl: data.apiUrl }),
|
|
57
|
+
...(data.method !== undefined && { method: data.method }),
|
|
58
|
+
...(data.prefix !== undefined && { prefix: data.prefix }),
|
|
59
|
+
...(data.description !== undefined && { description: data.description }),
|
|
60
|
+
...(encryptedCredential !== undefined && { encryptedCredential }),
|
|
61
|
+
};
|
|
62
|
+
connections[index] = updated;
|
|
63
|
+
const result = await ProjectModel.update({ _id: projectId }, { $set: { connections } });
|
|
64
|
+
if (!result)
|
|
65
|
+
throw new Error('Failed to update connection.');
|
|
66
|
+
return updated;
|
|
67
|
+
}
|
|
68
|
+
async function deleteConnection(config, projectId, connectionId) {
|
|
69
|
+
const ProjectModel = (0, manager_1.getModel)('projects');
|
|
70
|
+
const project = await ProjectModel.findOne({ _id: projectId });
|
|
71
|
+
if (!project) {
|
|
72
|
+
throw new Error('Project not found.');
|
|
73
|
+
}
|
|
74
|
+
const updatedProject = await ProjectModel.update({ _id: projectId }, { $pull: { connections: { id: connectionId } } });
|
|
75
|
+
if (!updatedProject) {
|
|
76
|
+
throw new Error('Failed to delete connection from project.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { MongoClient } from 'mongodb';
|
|
3
|
+
import { StreamByConfig, Auth, DbConnection, StorageConnection, StorageAdapter } from '../types';
|
|
4
|
+
export type ResolvedDbClient = {
|
|
5
|
+
client: Pool | MongoClient;
|
|
6
|
+
type: 'sql' | 'nosql';
|
|
7
|
+
ephemeral: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type ResolveError = {
|
|
10
|
+
error: string;
|
|
11
|
+
status: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function findDbConnection(project: any, connId: string): DbConnection | undefined;
|
|
14
|
+
export declare function findStorageConnection(project: any, connId: string): StorageConnection | undefined;
|
|
15
|
+
export declare function resolveDbConnectionClient(project: any, connId: string, config: StreamByConfig, auth: Auth): Promise<ResolvedDbClient | ResolveError>;
|
|
16
|
+
export declare function releaseDbClient(resolved: ResolvedDbClient): Promise<void>;
|
|
17
|
+
export declare function resolveStorageAdapter(project: any, connId: string, config: StreamByConfig & {
|
|
18
|
+
adapter?: StorageAdapter;
|
|
19
|
+
}, auth: Auth): Promise<StorageAdapter | ResolveError>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findDbConnection = findDbConnection;
|
|
4
|
+
exports.findStorageConnection = findStorageConnection;
|
|
5
|
+
exports.resolveDbConnectionClient = resolveDbConnectionClient;
|
|
6
|
+
exports.releaseDbClient = releaseDbClient;
|
|
7
|
+
exports.resolveStorageAdapter = resolveStorageAdapter;
|
|
8
|
+
const pg_1 = require("pg");
|
|
9
|
+
const mongodb_1 = require("mongodb");
|
|
10
|
+
const connectionManager_1 = require("../adapters/database/connectionManager");
|
|
11
|
+
const builtinAccess_1 = require("../utils/builtinAccess");
|
|
12
|
+
const userIntegration_1 = require("./userIntegration");
|
|
13
|
+
const encryption_1 = require("../utils/encryption");
|
|
14
|
+
const storage_1 = require("../providers/storage");
|
|
15
|
+
const s3_1 = require("../adapters/storage/s3");
|
|
16
|
+
function findDbConnection(project, connId) {
|
|
17
|
+
return project.dbConnections?.find((c) => c.id === connId);
|
|
18
|
+
}
|
|
19
|
+
function findStorageConnection(project, connId) {
|
|
20
|
+
return project.storageConnections?.find((c) => c.id === connId);
|
|
21
|
+
}
|
|
22
|
+
// `connId` must be the `id` of a real row in project.dbConnections — never a bare
|
|
23
|
+
// config id (config.databases[].id). Builtin rows reuse the shared pooled client keyed by
|
|
24
|
+
// conn.integrationId (config id); BYOC/manual rows open a short-lived client from their own
|
|
25
|
+
// decrypted credential, which the caller must close via `releaseDbClient` once done.
|
|
26
|
+
async function resolveDbConnectionClient(project, connId, config, auth) {
|
|
27
|
+
const conn = findDbConnection(project, connId);
|
|
28
|
+
if (!conn)
|
|
29
|
+
return { error: 'DB connection not found', status: 404 };
|
|
30
|
+
if (conn.source === 'builtin') {
|
|
31
|
+
const builtinId = conn.integrationId;
|
|
32
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'database'))) {
|
|
33
|
+
return { error: 'Access to this built-in database is not permitted', status: 403 };
|
|
34
|
+
}
|
|
35
|
+
const db = (config.databases ?? []).find(d => d.id === builtinId);
|
|
36
|
+
if (!db)
|
|
37
|
+
return { error: `Database '${builtinId}' not found in config`, status: 404 };
|
|
38
|
+
try {
|
|
39
|
+
const { client, type } = (0, connectionManager_1.getConnection)(builtinId);
|
|
40
|
+
return { client: client, type, ephemeral: false };
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
return { error: e.message, status: 503 };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
let connectionString;
|
|
47
|
+
if (conn.source === 'integration') {
|
|
48
|
+
if (!conn.integrationId)
|
|
49
|
+
return { error: 'Connection is missing its integrationId', status: 500 };
|
|
50
|
+
const credential = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId);
|
|
51
|
+
if (!credential)
|
|
52
|
+
return { error: 'Integration not found', status: 400 };
|
|
53
|
+
connectionString = credential;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
57
|
+
return { error: 'Encryption key is not set', status: 500 };
|
|
58
|
+
if (!conn.encryptedCredential)
|
|
59
|
+
return { error: 'Connection has no stored credential', status: 400 };
|
|
60
|
+
connectionString = (0, encryption_1.decrypt)(conn.encryptedCredential);
|
|
61
|
+
}
|
|
62
|
+
const type = conn.dbType === 'postgresql' ? 'sql' : 'nosql';
|
|
63
|
+
try {
|
|
64
|
+
if (type === 'sql') {
|
|
65
|
+
const pool = new pg_1.Pool({ connectionString });
|
|
66
|
+
await pool.query('SELECT 1');
|
|
67
|
+
return { client: pool, type, ephemeral: true };
|
|
68
|
+
}
|
|
69
|
+
const client = new mongodb_1.MongoClient(connectionString);
|
|
70
|
+
await client.connect();
|
|
71
|
+
return { client, type, ephemeral: true };
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return { error: `Failed to connect: ${e.message}`, status: 503 };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Only closes clients this resolver opened itself (`ephemeral: true`) — the shared pooled
|
|
78
|
+
// builtin clients are owned by connectionManager and must outlive a single request.
|
|
79
|
+
async function releaseDbClient(resolved) {
|
|
80
|
+
if (!resolved.ephemeral)
|
|
81
|
+
return;
|
|
82
|
+
if (resolved.type === 'sql')
|
|
83
|
+
await resolved.client.end();
|
|
84
|
+
else
|
|
85
|
+
await resolved.client.close();
|
|
86
|
+
}
|
|
87
|
+
async function resolveStorageAdapter(project, connId, config, auth) {
|
|
88
|
+
const conn = findStorageConnection(project, connId);
|
|
89
|
+
if (!conn)
|
|
90
|
+
return { error: 'Storage connection not found', status: 404 };
|
|
91
|
+
if (conn.source === 'builtin') {
|
|
92
|
+
const builtinId = conn.integrationId;
|
|
93
|
+
if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, builtinId, config, 'storage'))) {
|
|
94
|
+
return { error: 'Access to this built-in storage is not permitted', status: 403 };
|
|
95
|
+
}
|
|
96
|
+
const provider = config.storageProviders.find(p => p.id === builtinId);
|
|
97
|
+
if (!provider)
|
|
98
|
+
return { error: 'Storage provider not found', status: 404 };
|
|
99
|
+
if (provider.type === 's3')
|
|
100
|
+
return new s3_1.S3Adapter(provider.config);
|
|
101
|
+
return config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
|
|
102
|
+
}
|
|
103
|
+
if (conn.source === 'integration') {
|
|
104
|
+
if (!conn.integrationId)
|
|
105
|
+
return { error: 'Connection is missing its integrationId', status: 500 };
|
|
106
|
+
try {
|
|
107
|
+
const s3Config = await (0, userIntegration_1.getDecryptedIntegrationCredentialById)(conn.integrationId);
|
|
108
|
+
if (!s3Config)
|
|
109
|
+
return { error: 'Integration not found', status: 400 };
|
|
110
|
+
return new s3_1.S3Adapter(s3Config);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!(0, encryption_1.isEncryptionKeySet)())
|
|
117
|
+
return { error: 'Encryption key not set', status: 500 };
|
|
118
|
+
if (!conn.encryptedCredential)
|
|
119
|
+
return { error: 'Connection has no stored credential', status: 400 };
|
|
120
|
+
try {
|
|
121
|
+
const s3Config = JSON.parse((0, encryption_1.decrypt)(conn.encryptedCredential));
|
|
122
|
+
return new s3_1.S3Adapter(s3Config);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return { error: `Failed to initialize storage adapter: ${e.message}`, status: 500 };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { NodeSchema, StreamByConfig } from '../types';
|
|
2
|
+
export declare class NodeExecutionError extends Error {
|
|
3
|
+
nodeId: string;
|
|
4
|
+
nodeType: string;
|
|
5
|
+
constructor(nodeId: string, nodeType: string, message: string);
|
|
6
|
+
}
|
|
2
7
|
export declare function executeExport(nodeSchema: NodeSchema, project: any, config: StreamByConfig): Promise<any>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeExecutionError = void 0;
|
|
3
4
|
exports.executeExport = executeExport;
|
|
4
|
-
const connectionManager_1 = require("../adapters/database/connectionManager");
|
|
5
5
|
const encryption_1 = require("../utils/encryption");
|
|
6
6
|
const dbConnection_1 = require("./dbConnection");
|
|
7
|
+
const connectionResolver_1 = require("./connectionResolver");
|
|
7
8
|
const jobQueue_1 = require("./jobQueue");
|
|
8
9
|
const mediaProcessor_1 = require("./mediaProcessor");
|
|
9
10
|
const vfxProcessor_1 = require("./vfxProcessor");
|
|
@@ -75,6 +76,21 @@ function getTarget(nodes, edges, sourceId, sourceHandle) {
|
|
|
75
76
|
const edge = edges.find(e => e.source === sourceId && e.sourceHandle === sourceHandle);
|
|
76
77
|
return edge ? (nodes.find(n => n.id === edge.target) ?? null) : null;
|
|
77
78
|
}
|
|
79
|
+
class NodeExecutionError extends Error {
|
|
80
|
+
constructor(nodeId, nodeType, message) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.nodeId = nodeId;
|
|
83
|
+
this.nodeType = nodeType;
|
|
84
|
+
this.name = 'NodeExecutionError';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.NodeExecutionError = NodeExecutionError;
|
|
88
|
+
// Export execution is public (gated by allowedOrigin, not user identity — see
|
|
89
|
+
// export.ts:339) — there is no real Auth to pass through. This sentinel satisfies
|
|
90
|
+
// resolveDbConnectionClient's signature without adding a gate that never existed;
|
|
91
|
+
// canUseBuiltin gating is for connection *management* by a user, not for serving an
|
|
92
|
+
// already-published export.
|
|
93
|
+
const SYSTEM_AUTH = { userId: 'system', username: 'system', role: 'admin' };
|
|
78
94
|
async function executeExport(nodeSchema, project, config) {
|
|
79
95
|
const nodes = nodeSchema.nodes;
|
|
80
96
|
const edges = nodeSchema.edges;
|
|
@@ -94,42 +110,41 @@ async function executeExport(nodeSchema, project, config) {
|
|
|
94
110
|
const tableName = (node.data?.tableName || node.data?.subtitle || node.data?.label);
|
|
95
111
|
if (!tableName)
|
|
96
112
|
continue;
|
|
113
|
+
// dataSourceNode always declares its own database connection explicitly (persistence:
|
|
114
|
+
// 'database' in types/nodePersistence.ts) — a project has no notion of a "default"
|
|
115
|
+
// connection, since a project can have many database/storage/API providers connected.
|
|
97
116
|
const connectionId = node.data?.connectionId;
|
|
117
|
+
if (!connectionId) {
|
|
118
|
+
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}) has no database connection — connect one on the node.`);
|
|
119
|
+
}
|
|
98
120
|
const recordId = node.data?.recordId;
|
|
99
121
|
const projectIdentifier = project._id?.toString() ?? project.id;
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
dataResults.push(
|
|
122
|
+
const resolved = await (0, connectionResolver_1.resolveDbConnectionClient)(project, connectionId, config, SYSTEM_AUTH);
|
|
123
|
+
if ('error' in resolved) {
|
|
124
|
+
throw new NodeExecutionError(node.id, node.type, `Node '${node.id}' (${node.type}): ${resolved.error}`);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
dataResults.push(recordId
|
|
128
|
+
? await (0, dbConnection_1.queryRecordByIdInternal)(resolved.client, resolved.type, tableName, recordId, projectIdentifier)
|
|
129
|
+
: await (0, dbConnection_1.queryRecordsInternal)(resolved.client, resolved.type, tableName, 500, 0, projectIdentifier));
|
|
106
130
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const targetDb = config.databases?.find((db) => db.type === project.dbType && db.main)
|
|
110
|
-
?? config.databases?.find((db) => db.type === project.dbType);
|
|
111
|
-
if (!targetDb)
|
|
112
|
-
throw new Error(`No database connection for type ${project.dbType}`);
|
|
113
|
-
const { client, type } = (0, connectionManager_1.getConnection)(targetDb.id);
|
|
114
|
-
dataResults.push(await fetchData(client, type));
|
|
131
|
+
finally {
|
|
132
|
+
await (0, connectionResolver_1.releaseDbClient)(resolved);
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
|
-
else if (node.type === '
|
|
118
|
-
const
|
|
119
|
-
if (!
|
|
135
|
+
else if (node.type === 'connectionNode') {
|
|
136
|
+
const connection = project.connections?.find((c) => c.id === node.data?.connectionId);
|
|
137
|
+
if (!connection)
|
|
120
138
|
continue;
|
|
121
139
|
const headers = { 'Content-Type': 'application/json' };
|
|
122
|
-
if (
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const prefix = apiConnection.prefix ? `${apiConnection.prefix} ` : '';
|
|
127
|
-
headers['Authorization'] = `${prefix}${decrypted}`;
|
|
128
|
-
}
|
|
140
|
+
if (connection.encryptedCredential && (0, encryption_1.isEncryptionKeySet)()) {
|
|
141
|
+
const decrypted = (0, encryption_1.decrypt)(connection.encryptedCredential);
|
|
142
|
+
const prefix = connection.prefix ? `${connection.prefix} ` : '';
|
|
143
|
+
headers['Authorization'] = `${prefix}${decrypted}`;
|
|
129
144
|
}
|
|
130
|
-
const response = await fetch(
|
|
145
|
+
const response = await fetch(connection.apiUrl, { method: connection.method || 'GET', headers });
|
|
131
146
|
if (!response.ok)
|
|
132
|
-
throw new Error(`
|
|
147
|
+
throw new Error(`Connection fetch failed: ${response.statusText}`);
|
|
133
148
|
dataResults.push(await response.json());
|
|
134
149
|
}
|
|
135
150
|
}
|
|
@@ -173,6 +188,17 @@ async function executeExport(nodeSchema, project, config) {
|
|
|
173
188
|
payload = { ...payload, jobId: job.jobId, jobType: 'thumbnail' };
|
|
174
189
|
}
|
|
175
190
|
else if (processNode.type === 'ingestNode' && fileId) {
|
|
191
|
+
// ingestNode always declares its own storage connection explicitly (persistence:
|
|
192
|
+
// 'storage' in types/nodePersistence.ts) — a project has no notion of a "default"
|
|
193
|
+
// connection, since a project can have many database/storage/API providers connected.
|
|
194
|
+
const storageConnectionId = nodeData.storageConnectionId;
|
|
195
|
+
if (!storageConnectionId) {
|
|
196
|
+
throw new NodeExecutionError(processNode.id, processNode.type, `Node '${processNode.id}' (${processNode.type}) has no storage connection — connect one on the node.`);
|
|
197
|
+
}
|
|
198
|
+
const storageResolved = await (0, connectionResolver_1.resolveStorageAdapter)(project, storageConnectionId, config, SYSTEM_AUTH);
|
|
199
|
+
if ('error' in storageResolved) {
|
|
200
|
+
throw new NodeExecutionError(processNode.id, processNode.type, `Node '${processNode.id}' (${processNode.type}): ${storageResolved.error}`);
|
|
201
|
+
}
|
|
176
202
|
const job = (0, jobQueue_1.createJob)('ingest', systemUserId, projectId, nodeData);
|
|
177
203
|
setImmediate(() => (0, mediaProcessor_1.runIngestJob)(job.jobId, fileId, projectId));
|
|
178
204
|
payload = { ...payload, jobId: job.jobId, jobType: 'ingest' };
|
|
@@ -22,7 +22,7 @@ async function classifyIntegrationId(id, auth, config, projectId) {
|
|
|
22
22
|
kind: 'database',
|
|
23
23
|
connection: {
|
|
24
24
|
id: crypto_1.default.randomUUID(), name: builtinAccess_1.BUILTIN_DB_DISPLAY[db.type] ?? db.type, dbType: db.type === 'sql' ? 'postgresql' : 'mongodb',
|
|
25
|
-
|
|
25
|
+
projectId, createdAt: now, isBuiltin: true, integrationId: id, source: 'builtin',
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -35,7 +35,7 @@ async function classifyIntegrationId(id, auth, config, projectId) {
|
|
|
35
35
|
kind: 'storage',
|
|
36
36
|
connection: {
|
|
37
37
|
id: crypto_1.default.randomUUID(), name: builtinAccess_1.BUILTIN_STORAGE_DISPLAY[provider.type] ?? provider.type, type: provider.type,
|
|
38
|
-
|
|
38
|
+
projectId, createdAt: now, isBuiltin: true, integrationId: id, source: 'builtin',
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
}
|
|
@@ -49,7 +49,7 @@ async function classifyIntegrationId(id, auth, config, projectId) {
|
|
|
49
49
|
kind: 'database',
|
|
50
50
|
connection: {
|
|
51
51
|
id: crypto_1.default.randomUUID(), name: integration.name, dbType: integration.provider,
|
|
52
|
-
|
|
52
|
+
projectId, createdAt: now, isBuiltin: false, integrationId: id, source: 'integration',
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
55
|
}
|
|
@@ -57,7 +57,7 @@ async function classifyIntegrationId(id, auth, config, projectId) {
|
|
|
57
57
|
kind: 'storage',
|
|
58
58
|
connection: {
|
|
59
59
|
id: crypto_1.default.randomUUID(), name: integration.name, type: integration.provider,
|
|
60
|
-
|
|
60
|
+
projectId, createdAt: now, isBuiltin: false, integrationId: id, source: 'integration',
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface StorageConnection {
|
|
|
12
12
|
id: string;
|
|
13
13
|
name: string;
|
|
14
14
|
type: StorageConnectionType;
|
|
15
|
-
|
|
15
|
+
encryptedCredential?: string;
|
|
16
16
|
projectId: string;
|
|
17
17
|
createdAt: Date;
|
|
18
18
|
description?: string;
|
|
@@ -73,29 +73,35 @@ export interface ProjectInfo {
|
|
|
73
73
|
allowUpload?: boolean;
|
|
74
74
|
allowSharing?: boolean;
|
|
75
75
|
};
|
|
76
|
-
|
|
77
|
-
apiConnections?: ApiConnection[];
|
|
76
|
+
connections?: Connection[];
|
|
78
77
|
dbConnections?: DbConnection[];
|
|
79
78
|
storageConnections?: StorageConnection[];
|
|
80
79
|
pipelines?: PipelineRef[];
|
|
81
80
|
builtinBackfilledAt?: Date;
|
|
81
|
+
workflow?: Workflow;
|
|
82
82
|
}
|
|
83
|
-
export
|
|
83
|
+
export type NodePersistence = 'database' | 'storage' | 'both' | 'none';
|
|
84
|
+
export interface Workflow {
|
|
84
85
|
id: string;
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
name: string;
|
|
87
|
+
description?: string;
|
|
88
|
+
status: string;
|
|
89
|
+
projectId: string;
|
|
90
|
+
nodeSchema: NodeSchema | null;
|
|
91
|
+
createdAt: Date;
|
|
92
|
+
updatedAt: Date;
|
|
87
93
|
}
|
|
88
|
-
export type
|
|
89
|
-
export interface
|
|
94
|
+
export type ConnectionMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
95
|
+
export interface Connection {
|
|
90
96
|
id: string;
|
|
91
97
|
name: string;
|
|
92
98
|
apiUrl: string;
|
|
93
|
-
method:
|
|
99
|
+
method: ConnectionMethod;
|
|
94
100
|
projectId: string;
|
|
95
101
|
createdAt: Date;
|
|
96
102
|
prefix?: string;
|
|
97
103
|
description?: string;
|
|
98
|
-
|
|
104
|
+
encryptedCredential?: string;
|
|
99
105
|
}
|
|
100
106
|
export interface ProjectListInfo {
|
|
101
107
|
id: string;
|
|
@@ -153,7 +159,7 @@ export interface DbConnection {
|
|
|
153
159
|
id: string;
|
|
154
160
|
name: string;
|
|
155
161
|
dbType: ExternalDbType;
|
|
156
|
-
|
|
162
|
+
encryptedCredential?: string;
|
|
157
163
|
projectId: string;
|
|
158
164
|
createdAt: Date;
|
|
159
165
|
description?: string;
|
|
@@ -214,7 +220,7 @@ export interface Export {
|
|
|
214
220
|
fields?: FieldDefinition[];
|
|
215
221
|
jsonData?: any;
|
|
216
222
|
apiUrl?: string;
|
|
217
|
-
|
|
223
|
+
encryptedCredential?: string;
|
|
218
224
|
prefix?: string;
|
|
219
225
|
private?: boolean;
|
|
220
226
|
allowedOrigin?: string[];
|
|
@@ -244,7 +250,7 @@ export interface DistributionConnection {
|
|
|
244
250
|
id: string;
|
|
245
251
|
name: string;
|
|
246
252
|
target: DistributionTarget;
|
|
247
|
-
|
|
253
|
+
encryptedCredential?: string;
|
|
248
254
|
projectId: string;
|
|
249
255
|
config: Record<string, string>;
|
|
250
256
|
createdAt: Date;
|
|
@@ -289,7 +295,7 @@ export interface RenderFarmConnection {
|
|
|
289
295
|
name: string;
|
|
290
296
|
provider: RenderFarmProvider;
|
|
291
297
|
apiUrl: string;
|
|
292
|
-
|
|
298
|
+
encryptedCredential?: string;
|
|
293
299
|
projectId: string;
|
|
294
300
|
createdAt: Date;
|
|
295
301
|
description?: string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NODE_PERSISTENCE = void 0;
|
|
4
|
+
exports.getNodePersistence = getNodePersistence;
|
|
5
|
+
// Only node types that write to a real database/storage connection need an entry here.
|
|
6
|
+
// Process nodes (transcodeNode, captionNode, etc.) operate on a fileId already resolved by
|
|
7
|
+
// the internal job pipeline (mediaProcessor.ts), not directly on project connections, so
|
|
8
|
+
// they're 'none' — same for filter/deliverable/distribution/annotation/connection nodes.
|
|
9
|
+
exports.NODE_PERSISTENCE = {
|
|
10
|
+
dataSourceNode: 'database',
|
|
11
|
+
ingestNode: 'storage',
|
|
12
|
+
};
|
|
13
|
+
function getNodePersistence(nodeType) {
|
|
14
|
+
return exports.NODE_PERSISTENCE[nodeType] ?? 'none';
|
|
15
|
+
}
|
package/dist/utils/sanitize.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
export declare function sanitizeConnection<T extends {
|
|
2
|
+
encryptedCredential?: string;
|
|
3
|
+
}>(conn: T): Omit<T, 'encryptedCredential'> & {
|
|
4
|
+
hasCredential: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function sanitizeConnections<T extends {
|
|
7
|
+
encryptedCredential?: string;
|
|
8
|
+
}>(conns: T[] | undefined): (Omit<T, 'encryptedCredential'> & {
|
|
9
|
+
hasCredential: boolean;
|
|
10
|
+
})[];
|
|
1
11
|
export declare function sanitizeProject(project: any): any;
|
|
2
12
|
export declare function sanitizeUserIntegration(integration: any): any;
|