streamby-core 0.33.0 → 0.35.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 +100 -0
- package/LICENSE +661 -0
- package/README.md +111 -11
- package/dist/adapters/database/connectionManager.js +18 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/middleware/createRouter.js +19 -2
- package/dist/middleware/routes/ai.js +3 -3
- package/dist/middleware/routes/{credential.d.ts → apiToken.d.ts} +1 -1
- package/dist/middleware/routes/apiToken.js +58 -0
- package/dist/middleware/routes/connection.js +3 -12
- package/dist/middleware/routes/dbConnection.js +7 -26
- package/dist/middleware/routes/distribution.js +12 -5
- package/dist/middleware/routes/export.js +5 -4
- package/dist/middleware/routes/media.js +27 -10
- package/dist/middleware/routes/member.js +3 -12
- package/dist/middleware/routes/pipeline.js +3 -3
- package/dist/middleware/routes/production.js +9 -9
- package/dist/middleware/routes/project.js +12 -25
- package/dist/middleware/routes/renderFarm.js +13 -6
- package/dist/middleware/routes/review.js +4 -4
- package/dist/middleware/routes/rights.js +1 -1
- package/dist/middleware/routes/storage.js +21 -15
- package/dist/middleware/routes/storageConnection.js +2 -8
- package/dist/middleware/routes/workflow.js +1 -1
- package/dist/services/apiTokens.d.ts +47 -0
- package/dist/services/apiTokens.js +110 -0
- package/dist/services/auth.d.ts +5 -3
- package/dist/services/auth.js +49 -11
- package/dist/services/connectionResolver.js +2 -2
- package/dist/services/executor.d.ts +4 -1
- package/dist/services/executor.js +29 -11
- package/dist/services/reencryptCredentials.d.ts +5 -0
- package/dist/services/reencryptCredentials.js +74 -0
- package/dist/services/userIntegration.d.ts +2 -2
- package/dist/services/userIntegration.js +7 -1
- package/dist/services/wsHub.js +6 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/utils/auth.d.ts +15 -1
- package/dist/utils/auth.js +21 -6
- package/dist/utils/encryption.d.ts +6 -1
- package/dist/utils/encryption.js +80 -27
- package/dist/utils/serviceEndpoints.d.ts +2 -0
- package/dist/utils/serviceEndpoints.js +26 -0
- package/package.json +4 -3
- package/dist/middleware/routes/apiConnection.d.ts +0 -3
- package/dist/middleware/routes/apiConnection.js +0 -100
- package/dist/middleware/routes/credential.js +0 -80
- package/dist/services/apiConnection.d.ts +0 -18
- package/dist/services/apiConnection.js +0 -65
- package/dist/services/credential.d.ts +0 -8
- package/dist/services/credential.js +0 -70
- package/dist/services/pipeline.d.ts +0 -2
- package/dist/services/pipeline.js +0 -292
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.executePipeline = executePipeline;
|
|
4
|
-
const connectionManager_1 = require("../adapters/database/connectionManager");
|
|
5
|
-
const encryption_1 = require("../utils/encryption");
|
|
6
|
-
const dbConnection_1 = require("./dbConnection");
|
|
7
|
-
const jobQueue_1 = require("./jobQueue");
|
|
8
|
-
const mediaProcessor_1 = require("./mediaProcessor");
|
|
9
|
-
const vfxProcessor_1 = require("./vfxProcessor");
|
|
10
|
-
const aiProcessor_1 = require("./aiProcessor");
|
|
11
|
-
const storage_1 = require("../providers/storage");
|
|
12
|
-
function applyFilterConfig(payload, config) {
|
|
13
|
-
const isArr = Array.isArray(payload);
|
|
14
|
-
let r = isArr ? [...payload] : payload;
|
|
15
|
-
if (config.conditions?.length) {
|
|
16
|
-
const matches = (item) => config.conditions.every(c => {
|
|
17
|
-
const v = item?.[c.field];
|
|
18
|
-
switch (c.op) {
|
|
19
|
-
case 'eq': return String(v) === c.value;
|
|
20
|
-
case 'neq': return String(v) !== c.value;
|
|
21
|
-
case 'gt': return Number(v) > Number(c.value);
|
|
22
|
-
case 'lt': return Number(v) < Number(c.value);
|
|
23
|
-
case 'gte': return Number(v) >= Number(c.value);
|
|
24
|
-
case 'lte': return Number(v) <= Number(c.value);
|
|
25
|
-
case 'contains': return String(v).includes(c.value);
|
|
26
|
-
case 'startsWith': return String(v).startsWith(c.value);
|
|
27
|
-
case 'endsWith': return String(v).endsWith(c.value);
|
|
28
|
-
default: return true;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
r = isArr ? r.filter(matches) : (matches(r) ? r : null);
|
|
32
|
-
if (r === null)
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
if (config.includeFields?.length) {
|
|
36
|
-
const pick = (item) => {
|
|
37
|
-
if (!item || typeof item !== 'object')
|
|
38
|
-
return item;
|
|
39
|
-
const o = {};
|
|
40
|
-
for (const f of config.includeFields)
|
|
41
|
-
if (f in item)
|
|
42
|
-
o[f] = item[f];
|
|
43
|
-
return o;
|
|
44
|
-
};
|
|
45
|
-
r = isArr ? r.map(pick) : pick(r);
|
|
46
|
-
}
|
|
47
|
-
if (config.renameFields?.length) {
|
|
48
|
-
const ren = (item) => {
|
|
49
|
-
if (!item || typeof item !== 'object')
|
|
50
|
-
return item;
|
|
51
|
-
const o = { ...item };
|
|
52
|
-
for (const { from, to } of config.renameFields) {
|
|
53
|
-
if (from in o) {
|
|
54
|
-
o[to] = o[from];
|
|
55
|
-
delete o[from];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return o;
|
|
59
|
-
};
|
|
60
|
-
r = isArr ? r.map(ren) : ren(r);
|
|
61
|
-
}
|
|
62
|
-
if (config.limit && isArr)
|
|
63
|
-
r = r.slice(0, config.limit);
|
|
64
|
-
if (config.wrapKey)
|
|
65
|
-
r = { [config.wrapKey]: r };
|
|
66
|
-
return r;
|
|
67
|
-
}
|
|
68
|
-
function getSources(nodes, edges, targetId, targetHandle) {
|
|
69
|
-
return edges
|
|
70
|
-
.filter(e => e.target === targetId && e.targetHandle === targetHandle)
|
|
71
|
-
.map(e => nodes.find(n => n.id === e.source))
|
|
72
|
-
.filter((n) => n !== undefined);
|
|
73
|
-
}
|
|
74
|
-
function getTarget(nodes, edges, sourceId, sourceHandle) {
|
|
75
|
-
const edge = edges.find(e => e.source === sourceId && e.sourceHandle === sourceHandle);
|
|
76
|
-
return edge ? (nodes.find(n => n.id === edge.target) ?? null) : null;
|
|
77
|
-
}
|
|
78
|
-
async function executePipeline(nodeSchema, project, config) {
|
|
79
|
-
const nodes = nodeSchema.nodes;
|
|
80
|
-
const edges = nodeSchema.edges;
|
|
81
|
-
// 1. Data layer — nodes connected to streamby in-bottom
|
|
82
|
-
const dataSources = getSources(nodes, edges, 'streamby', 'in-bottom');
|
|
83
|
-
const dataResults = [];
|
|
84
|
-
for (const node of dataSources) {
|
|
85
|
-
if (node.type === 'jsonInputNode') {
|
|
86
|
-
try {
|
|
87
|
-
dataResults.push(JSON.parse(node.data?.jsonString || 'null'));
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
dataResults.push(null);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else if (node.type === 'dataSourceNode') {
|
|
94
|
-
const tableName = (node.data?.tableName || node.data?.subtitle || node.data?.label);
|
|
95
|
-
if (!tableName)
|
|
96
|
-
continue;
|
|
97
|
-
const connectionId = node.data?.connectionId;
|
|
98
|
-
const recordId = node.data?.recordId;
|
|
99
|
-
const projectIdentifier = project._id?.toString() ?? project.id;
|
|
100
|
-
const fetchData = async (client, type) => recordId
|
|
101
|
-
? (0, dbConnection_1.queryRecordByIdInternal)(client, type, tableName, recordId, projectIdentifier)
|
|
102
|
-
: (0, dbConnection_1.queryRecordsInternal)(client, type, tableName, 500, 0, projectIdentifier);
|
|
103
|
-
if (connectionId) {
|
|
104
|
-
const { client, type } = (0, connectionManager_1.getConnection)(connectionId);
|
|
105
|
-
dataResults.push(await fetchData(client, type));
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// legacy fallback for nodes saved before connectionId was introduced
|
|
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));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else if (node.type === 'apiConnectionNode') {
|
|
118
|
-
const apiConnection = project.apiConnections?.find((c) => c.id === node.data?.connectionId);
|
|
119
|
-
if (!apiConnection)
|
|
120
|
-
continue;
|
|
121
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
122
|
-
if (apiConnection.credentialId && (0, encryption_1.isEncryptionKeySet)()) {
|
|
123
|
-
const credential = project.credentials?.find((c) => c.id === apiConnection.credentialId);
|
|
124
|
-
if (credential) {
|
|
125
|
-
const decrypted = (0, encryption_1.decrypt)(credential.encryptedValue);
|
|
126
|
-
const prefix = apiConnection.prefix ? `${apiConnection.prefix} ` : '';
|
|
127
|
-
headers['Authorization'] = `${prefix}${decrypted}`;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const response = await fetch(apiConnection.apiUrl, { method: apiConnection.method || 'GET', headers });
|
|
131
|
-
if (!response.ok)
|
|
132
|
-
throw new Error(`API connection fetch failed: ${response.statusText}`);
|
|
133
|
-
dataResults.push(await response.json());
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
let payload = dataResults.length === 1 ? dataResults[0] : dataResults;
|
|
137
|
-
// 2. Process layer — chain from streamby out-top → processNode in-process → out-process → ...
|
|
138
|
-
const adapter = (0, storage_1.createStorageProvider)(config.storageProviders);
|
|
139
|
-
const projectId = project._id?.toString() ?? project.id;
|
|
140
|
-
// userId is not available in pipeline context; use a sentinel for job attribution
|
|
141
|
-
const systemUserId = 'pipeline';
|
|
142
|
-
let processNode = getTarget(nodes, edges, 'streamby', 'out-top');
|
|
143
|
-
while (processNode) {
|
|
144
|
-
const nodeData = processNode.data ?? {};
|
|
145
|
-
const fileId = nodeData.fileId;
|
|
146
|
-
if (processNode.type === 'transcodeNode' && fileId) {
|
|
147
|
-
const job = (0, jobQueue_1.createJob)('transcode', systemUserId, projectId, nodeData);
|
|
148
|
-
setImmediate(() => (0, mediaProcessor_1.runTranscodeJob)(job.jobId, fileId, projectId, {
|
|
149
|
-
codec: nodeData.codec,
|
|
150
|
-
resolution: nodeData.resolution,
|
|
151
|
-
outputFormat: nodeData.outputFormat,
|
|
152
|
-
bitrate: nodeData.bitrate,
|
|
153
|
-
audioCodec: nodeData.audioCodec,
|
|
154
|
-
}, adapter));
|
|
155
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'transcode' };
|
|
156
|
-
}
|
|
157
|
-
else if (processNode.type === 'captionNode' && fileId) {
|
|
158
|
-
const job = (0, jobQueue_1.createJob)('caption', systemUserId, projectId, nodeData);
|
|
159
|
-
setImmediate(() => (0, mediaProcessor_1.runCaptionJob)(job.jobId, fileId, projectId, {
|
|
160
|
-
sourceLanguage: nodeData.sourceLanguage,
|
|
161
|
-
outputFormat: nodeData.outputFormat,
|
|
162
|
-
provider: nodeData.provider,
|
|
163
|
-
}));
|
|
164
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'caption' };
|
|
165
|
-
}
|
|
166
|
-
else if (processNode.type === 'thumbnailNode' && fileId) {
|
|
167
|
-
const job = (0, jobQueue_1.createJob)('thumbnail', systemUserId, projectId, nodeData);
|
|
168
|
-
setImmediate(() => (0, mediaProcessor_1.runThumbnailJob)(job.jobId, fileId, projectId, {
|
|
169
|
-
timecode: nodeData.timecode,
|
|
170
|
-
resolution: nodeData.resolution,
|
|
171
|
-
strategy: nodeData.strategy,
|
|
172
|
-
}));
|
|
173
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'thumbnail' };
|
|
174
|
-
}
|
|
175
|
-
else if (processNode.type === 'ingestNode' && fileId) {
|
|
176
|
-
const job = (0, jobQueue_1.createJob)('ingest', systemUserId, projectId, nodeData);
|
|
177
|
-
setImmediate(() => (0, mediaProcessor_1.runIngestJob)(job.jobId, fileId, projectId));
|
|
178
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'ingest' };
|
|
179
|
-
}
|
|
180
|
-
else if (processNode.type === 'renderJobNode' && fileId) {
|
|
181
|
-
const job = (0, jobQueue_1.createJob)('render', systemUserId, projectId, nodeData);
|
|
182
|
-
setImmediate(() => (0, vfxProcessor_1.runRenderJob)(job.jobId, fileId, projectId, {
|
|
183
|
-
renderer: nodeData.renderer,
|
|
184
|
-
renderFarmConnectionId: nodeData.renderFarmConnectionId,
|
|
185
|
-
frameRange: nodeData.frameRange,
|
|
186
|
-
resolution: nodeData.resolution,
|
|
187
|
-
samples: nodeData.samples,
|
|
188
|
-
outputFormat: nodeData.outputFormat,
|
|
189
|
-
}));
|
|
190
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'render' };
|
|
191
|
-
}
|
|
192
|
-
else if (processNode.type === 'formatConvertNode' && fileId) {
|
|
193
|
-
const job = (0, jobQueue_1.createJob)('format-convert', systemUserId, projectId, nodeData);
|
|
194
|
-
setImmediate(() => (0, vfxProcessor_1.runFormatConvertJob)(job.jobId, fileId, projectId, {
|
|
195
|
-
inputFormat: nodeData.inputFormat,
|
|
196
|
-
outputFormat: nodeData.outputFormat,
|
|
197
|
-
applyTransforms: nodeData.applyTransforms,
|
|
198
|
-
embedTextures: nodeData.embedTextures,
|
|
199
|
-
}));
|
|
200
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'format-convert' };
|
|
201
|
-
}
|
|
202
|
-
else if (processNode.type === 'lodNode' && fileId) {
|
|
203
|
-
const job = (0, jobQueue_1.createJob)('lod', systemUserId, projectId, nodeData);
|
|
204
|
-
setImmediate(() => (0, vfxProcessor_1.runLodJob)(job.jobId, fileId, projectId, {
|
|
205
|
-
levels: nodeData.levels,
|
|
206
|
-
reductionRatios: nodeData.reductionRatios,
|
|
207
|
-
algorithm: nodeData.algorithm,
|
|
208
|
-
outputFormat: nodeData.outputFormat,
|
|
209
|
-
}));
|
|
210
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'lod' };
|
|
211
|
-
}
|
|
212
|
-
else if (processNode.type === 'qcCheckNode' && fileId) {
|
|
213
|
-
const checks = nodeData.checks ?? [];
|
|
214
|
-
const checkResults = checks.map((name) => ({
|
|
215
|
-
name, passed: true, value: 'ok', threshold: 'ok',
|
|
216
|
-
}));
|
|
217
|
-
payload = { ...payload, qcReport: { checks: checkResults, overallPassed: true } };
|
|
218
|
-
}
|
|
219
|
-
else if (processNode.type === 'transcriptionNode' && fileId) {
|
|
220
|
-
const job = (0, jobQueue_1.createJob)('transcription', systemUserId, projectId, nodeData);
|
|
221
|
-
setImmediate(() => (0, aiProcessor_1.runTranscriptionJob)(job.jobId, fileId, projectId, {
|
|
222
|
-
model: nodeData.model,
|
|
223
|
-
sourceLanguage: nodeData.sourceLanguage,
|
|
224
|
-
outputFormats: nodeData.outputFormats,
|
|
225
|
-
provider: nodeData.provider,
|
|
226
|
-
credentialId: nodeData.credentialId,
|
|
227
|
-
}));
|
|
228
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'transcription' };
|
|
229
|
-
}
|
|
230
|
-
else if (processNode.type === 'upscaleNode' && fileId) {
|
|
231
|
-
const job = (0, jobQueue_1.createJob)('upscale', systemUserId, projectId, nodeData);
|
|
232
|
-
setImmediate(() => (0, aiProcessor_1.runUpscaleJob)(job.jobId, fileId, projectId, {
|
|
233
|
-
scale: nodeData.scale,
|
|
234
|
-
model: nodeData.model,
|
|
235
|
-
mode: nodeData.mode,
|
|
236
|
-
credentialId: nodeData.credentialId,
|
|
237
|
-
}));
|
|
238
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'upscale' };
|
|
239
|
-
}
|
|
240
|
-
else if (processNode.type === 'proceduralAssetNode') {
|
|
241
|
-
const job = (0, jobQueue_1.createJob)('generate-asset', systemUserId, projectId, nodeData);
|
|
242
|
-
setImmediate(() => (0, aiProcessor_1.runGenerateAssetJob)(job.jobId, projectId, {
|
|
243
|
-
assetType: nodeData.assetType,
|
|
244
|
-
provider: nodeData.provider,
|
|
245
|
-
prompt: nodeData.prompt ?? '',
|
|
246
|
-
seed: nodeData.seed,
|
|
247
|
-
credentialId: nodeData.credentialId,
|
|
248
|
-
}));
|
|
249
|
-
payload = { ...payload, jobId: job.jobId, jobType: 'generate-asset' };
|
|
250
|
-
}
|
|
251
|
-
else if (processNode.type === 'pipelineSuggestNode') {
|
|
252
|
-
const suggestion = await (0, aiProcessor_1.buildPipelineSuggestion)({ nodes, edges }, Object.keys(nodeSchema));
|
|
253
|
-
payload = { ...payload, pipelineSuggestion: suggestion };
|
|
254
|
-
}
|
|
255
|
-
processNode = getTarget(nodes, edges, processNode.id, 'out-process');
|
|
256
|
-
}
|
|
257
|
-
// 3. Filter + output lane — streamby out-right → filterNode|annotationNode|deliverableNode|distributionNode
|
|
258
|
-
const outputLaneTypes = new Set(['filterNode', 'annotationNode', 'deliverableNode', 'distributionNode']);
|
|
259
|
-
let outputNode = getTarget(nodes, edges, 'streamby', 'out-right');
|
|
260
|
-
while (outputNode) {
|
|
261
|
-
if (outputNode.type === 'filterNode') {
|
|
262
|
-
const cfg = outputNode.data?.filterConfig;
|
|
263
|
-
if (cfg)
|
|
264
|
-
payload = applyFilterConfig(payload, cfg);
|
|
265
|
-
}
|
|
266
|
-
else if (outputNode.type === 'deliverableNode') {
|
|
267
|
-
payload = {
|
|
268
|
-
...payload,
|
|
269
|
-
deliverable: {
|
|
270
|
-
type: outputNode.data?.deliverableType ?? 'asset-bundle',
|
|
271
|
-
version: outputNode.data?.deliverableVersion ?? '1.0.0',
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
else if (outputNode.type === 'distributionNode') {
|
|
276
|
-
// Distribution is async — the actual publish is handled by the host worker
|
|
277
|
-
payload = {
|
|
278
|
-
...payload,
|
|
279
|
-
distribution: {
|
|
280
|
-
target: outputNode.data?.distributionTarget,
|
|
281
|
-
connectionId: outputNode.data?.distributionConnectionId,
|
|
282
|
-
channel: outputNode.data?.channel,
|
|
283
|
-
status: 'queued',
|
|
284
|
-
},
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
// annotationNode — pass-through; annotations are stored separately via the review API
|
|
288
|
-
const next = getTarget(nodes, edges, outputNode.id, 'out-filter');
|
|
289
|
-
outputNode = next && outputLaneTypes.has(next.type) ? next : null;
|
|
290
|
-
}
|
|
291
|
-
return payload;
|
|
292
|
-
}
|