streamby-core 0.37.0 → 0.39.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 (50) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/LICENSE +661 -661
  3. package/README.md +573 -512
  4. package/dist/adapters/database/connectionManager.js +124 -124
  5. package/dist/adapters/database/sql.js +1 -1
  6. package/dist/adapters/storage/s3.d.ts +3 -2
  7. package/dist/adapters/storage/s3.js +18 -5
  8. package/dist/mcp/server.js +12 -0
  9. package/dist/mcp/tools.d.ts +2 -0
  10. package/dist/mcp/tools.js +107 -14
  11. package/dist/mcp/workflowNodes.d.ts +24 -0
  12. package/dist/mcp/workflowNodes.js +48 -0
  13. package/dist/middleware/createRouter.js +2 -0
  14. package/dist/middleware/routes/apiConnection.d.ts +3 -0
  15. package/dist/middleware/routes/apiConnection.js +100 -0
  16. package/dist/middleware/routes/credential.d.ts +3 -0
  17. package/dist/middleware/routes/credential.js +80 -0
  18. package/dist/middleware/routes/deliverable.d.ts +3 -0
  19. package/dist/middleware/routes/deliverable.js +131 -0
  20. package/dist/middleware/routes/export.js +18 -0
  21. package/dist/middleware/routes/production.js +60 -67
  22. package/dist/middleware/routes/project.js +39 -11
  23. package/dist/middleware/routes/storage.js +115 -24
  24. package/dist/models/Model.js +9 -9
  25. package/dist/services/apiConnection.d.ts +18 -0
  26. package/dist/services/apiConnection.js +65 -0
  27. package/dist/services/apiTokenPermissions.js +7 -3
  28. package/dist/services/assetVersions.d.ts +48 -0
  29. package/dist/services/assetVersions.js +84 -0
  30. package/dist/services/credential.d.ts +8 -0
  31. package/dist/services/credential.js +70 -0
  32. package/dist/services/deliverables.d.ts +26 -0
  33. package/dist/services/deliverables.js +55 -0
  34. package/dist/services/mediaProcessor.js +3 -1
  35. package/dist/services/pipeline.d.ts +2 -0
  36. package/dist/services/pipeline.js +292 -0
  37. package/dist/services/production.d.ts +2 -0
  38. package/dist/services/production.js +13 -0
  39. package/dist/services/productionFields.d.ts +16 -0
  40. package/dist/services/productionFields.js +80 -0
  41. package/dist/services/signedUrls.d.ts +6 -0
  42. package/dist/services/signedUrls.js +9 -0
  43. package/dist/types/index.d.ts +7 -2
  44. package/dist/utils/crypto.d.ts +3 -0
  45. package/dist/utils/crypto.js +32 -0
  46. package/dist/utils/nosqlDb.d.ts +2 -0
  47. package/dist/utils/nosqlDb.js +11 -0
  48. package/dist/utils/sanitize.d.ts +1 -0
  49. package/dist/utils/sanitize.js +14 -0
  50. package/package.json +1 -1
@@ -0,0 +1,292 @@
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
+ }
@@ -0,0 +1,2 @@
1
+ /** A deleted project's sequences, shots, tasks and deliverables: only that project's. */
2
+ export declare function deleteProductionData(projectId: string): Promise<void>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteProductionData = deleteProductionData;
4
+ const nosqlDb_1 = require("../utils/nosqlDb");
5
+ /** A deleted project's sequences, shots, tasks and deliverables: only that project's. */
6
+ async function deleteProductionData(projectId) {
7
+ const db = (0, nosqlDb_1.nosqlDb)();
8
+ if (!db || !projectId)
9
+ return;
10
+ for (const name of ['production_tasks', 'production_shots', 'production_sequences', 'deliverables']) {
11
+ await db.collection(name).deleteMany({ projectId });
12
+ }
13
+ }
@@ -0,0 +1,16 @@
1
+ export declare const PRODUCTION_STATUSES: readonly ["todo", "in_progress", "review", "done"];
2
+ export declare const TASK_PRIORITIES: readonly ["low", "medium", "high"];
3
+ type Field = 'name' | 'description' | 'notes' | 'status' | 'priority' | 'assignees' | 'assignee' | 'dueDate' | 'order' | 'assetId' | 'exportId';
4
+ export type FieldsResult = {
5
+ values: Record<string, any>;
6
+ } | {
7
+ error: string;
8
+ };
9
+ /**
10
+ * The fields of `body` that `allowed` lists, checked and ready to save (`assignees` and
11
+ * `assignee` are saved as `assignedTo`). With `creating`, a name is required.
12
+ */
13
+ export declare function checkProductionFields(body: any, allowed: readonly Field[], project: any, { creating }?: {
14
+ creating?: boolean | undefined;
15
+ }): FieldsResult;
16
+ export {};
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TASK_PRIORITIES = exports.PRODUCTION_STATUSES = void 0;
4
+ exports.checkProductionFields = checkProductionFields;
5
+ const auth_1 = require("../utils/auth");
6
+ // What a production sequence, shot or task may be given, checked before it's saved: a name
7
+ // that isn't blank, a known status and priority, assignees who are members of the project, a
8
+ // due date that is a date.
9
+ exports.PRODUCTION_STATUSES = ['todo', 'in_progress', 'review', 'done'];
10
+ exports.TASK_PRIORITIES = ['low', 'medium', 'high'];
11
+ const MAX_NAME = 200;
12
+ const MAX_TEXT = 5000;
13
+ /**
14
+ * The fields of `body` that `allowed` lists, checked and ready to save (`assignees` and
15
+ * `assignee` are saved as `assignedTo`). With `creating`, a name is required.
16
+ */
17
+ function checkProductionFields(body, allowed, project, { creating = false } = {}) {
18
+ const values = {};
19
+ const given = (field) => body?.[field] !== undefined;
20
+ const has = (field) => allowed.includes(field);
21
+ if (creating && !given('name'))
22
+ return { error: 'name is required' };
23
+ if (has('name') && given('name')) {
24
+ if (typeof body.name !== 'string' || !body.name.trim())
25
+ return { error: 'name must not be blank' };
26
+ if (body.name.trim().length > MAX_NAME)
27
+ return { error: `name must be at most ${MAX_NAME} characters` };
28
+ values.name = body.name.trim();
29
+ }
30
+ for (const field of ['description', 'notes']) {
31
+ if (!has(field) || !given(field))
32
+ continue;
33
+ const value = body[field];
34
+ if (value !== null && (typeof value !== 'string' || value.length > MAX_TEXT)) {
35
+ return { error: `${field} must be text of at most ${MAX_TEXT} characters, or null` };
36
+ }
37
+ values[field] = value === null || !value.trim() ? null : value;
38
+ }
39
+ if (has('status') && given('status')) {
40
+ if (!exports.PRODUCTION_STATUSES.includes(body.status))
41
+ return { error: `status must be one of ${exports.PRODUCTION_STATUSES.join(', ')}` };
42
+ values.status = body.status;
43
+ }
44
+ if (has('priority') && given('priority')) {
45
+ if (!exports.TASK_PRIORITIES.includes(body.priority))
46
+ return { error: `priority must be one of ${exports.TASK_PRIORITIES.join(', ')}` };
47
+ values.priority = body.priority;
48
+ }
49
+ const isMember = (id) => typeof id === 'string' && !!(0, auth_1.activeMemberOf)(project, id);
50
+ if (has('assignees') && given('assignedTo')) {
51
+ const ids = body.assignedTo;
52
+ if (!Array.isArray(ids) || !ids.every(isMember))
53
+ return { error: 'assignedTo must be a list of members of the project' };
54
+ values.assignedTo = [...new Set(ids)];
55
+ }
56
+ if (has('assignee') && given('assignedTo')) {
57
+ if (body.assignedTo !== null && !isMember(body.assignedTo))
58
+ return { error: 'assignedTo must be a member of the project, or null' };
59
+ values.assignedTo = body.assignedTo;
60
+ }
61
+ if (has('dueDate') && given('dueDate')) {
62
+ const date = body.dueDate === null || body.dueDate === '' ? null : new Date(body.dueDate);
63
+ if (date && Number.isNaN(date.getTime()))
64
+ return { error: 'dueDate must be a date, or null' };
65
+ values.dueDate = date;
66
+ }
67
+ if (has('order') && given('order')) {
68
+ if (typeof body.order !== 'number' || !Number.isFinite(body.order))
69
+ return { error: 'order must be a number' };
70
+ values.order = body.order;
71
+ }
72
+ for (const field of ['assetId', 'exportId']) {
73
+ if (!has(field) || !given(field))
74
+ continue;
75
+ if (body[field] !== null && typeof body[field] !== 'string')
76
+ return { error: `${field} must be an id, or null` };
77
+ values[field] = body[field] || null;
78
+ }
79
+ return { values };
80
+ }
@@ -0,0 +1,6 @@
1
+ import { Auth, StreamByConfig } from '../types';
2
+ export declare const DEFAULT_TOKEN_SIGNED_URL_TTL: {
3
+ upload: number;
4
+ download: number;
5
+ };
6
+ export declare const signedUrlTtl: (auth: Auth | undefined, config: StreamByConfig, kind: "upload" | "download") => number | undefined;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.signedUrlTtl = exports.DEFAULT_TOKEN_SIGNED_URL_TTL = void 0;
4
+ // How long a signed storage URL works. A request made with an API token (an agent, the MCP
5
+ // server) gets short-lived ones; a browser session, the adapter's own (an hour to upload, a
6
+ // day to read).
7
+ exports.DEFAULT_TOKEN_SIGNED_URL_TTL = { upload: 15 * 60, download: 5 * 60 };
8
+ const signedUrlTtl = (auth, config, kind) => auth?.apiToken ? config.mcp?.signedUrlTtl?.[kind] ?? exports.DEFAULT_TOKEN_SIGNED_URL_TTL[kind] : undefined;
9
+ exports.signedUrlTtl = signedUrlTtl;
@@ -42,10 +42,11 @@ export interface StorageAdapter {
42
42
  deleteProjectDirectory: (projectId: string) => Promise<any>;
43
43
  getPresignedUrl?: (contentType: string, projectId: string) => Promise<any>;
44
44
  getPresignedProjectImageUrl?: (projectId: string) => Promise<any>;
45
- getPresignedUploadUrl?: (key: string, contentType: string) => Promise<string>;
45
+ getPresignedUploadUrl?: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
46
46
  listFilesByCategory?: (projectId: string, category: string) => Promise<StorageFileInfo[]>;
47
47
  deleteFile?: (key: string) => Promise<void>;
48
- getPresignedGetUrl?: (key: string) => Promise<string>;
48
+ copyFile?: (from: string, to: string) => Promise<void>;
49
+ getPresignedGetUrl?: (key: string, expiresIn?: number, downloadName?: string) => Promise<string>;
49
50
  }
50
51
  export interface FieldDefinition {
51
52
  name: string;
@@ -140,6 +141,10 @@ export interface StreamByConfig {
140
141
  windowMs: number;
141
142
  };
142
143
  apiBaseUrl?: string;
144
+ signedUrlTtl?: {
145
+ upload?: number;
146
+ download?: number;
147
+ };
143
148
  };
144
149
  websocket?: {
145
150
  server: WebSocketServer;
@@ -0,0 +1,3 @@
1
+ export declare function encrypt(text: string, secretKey: string): string;
2
+ export declare function decrypt(text: string, secretKey: string): string;
3
+ export declare function hash(text: string): string;
@@ -0,0 +1,32 @@
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.encrypt = encrypt;
7
+ exports.decrypt = decrypt;
8
+ exports.hash = hash;
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const ALGORITHM = 'aes-256-cbc';
11
+ const IV_LENGTH = 16;
12
+ function encrypt(text, secretKey) {
13
+ const iv = crypto_1.default.randomBytes(IV_LENGTH);
14
+ const key = crypto_1.default.createHash('sha256').update(String(secretKey)).digest('base64').substr(0, 32);
15
+ const cipher = crypto_1.default.createCipheriv(ALGORITHM, Buffer.from(key), iv);
16
+ let encrypted = cipher.update(text);
17
+ encrypted = Buffer.concat([encrypted, cipher.final()]);
18
+ return iv.toString('hex') + ':' + encrypted.toString('hex');
19
+ }
20
+ function decrypt(text, secretKey) {
21
+ const textParts = text.split(':');
22
+ const iv = Buffer.from(textParts.shift(), 'hex');
23
+ const encryptedText = Buffer.from(textParts.join(':'), 'hex');
24
+ const key = crypto_1.default.createHash('sha256').update(String(secretKey)).digest('base64').substr(0, 32);
25
+ const decipher = crypto_1.default.createDecipheriv(ALGORITHM, Buffer.from(key), iv);
26
+ let decrypted = decipher.update(encryptedText);
27
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
28
+ return decrypted.toString();
29
+ }
30
+ function hash(text) {
31
+ return crypto_1.default.createHash('sha256').update(text).digest('hex');
32
+ }
@@ -0,0 +1,2 @@
1
+ /** The NoSQL database the project's files and production live in, or null when there's none. */
2
+ export declare function nosqlDb(): import("mongodb").Db | null;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nosqlDb = nosqlDb;
4
+ const connectionManager_1 = require("../adapters/database/connectionManager");
5
+ /** The NoSQL database the project's files and production live in, or null when there's none. */
6
+ function nosqlDb() {
7
+ const ids = (0, connectionManager_1.getConnectedIds)().filter(id => (0, connectionManager_1.getConnection)(id).type === 'nosql');
8
+ if (!ids.length)
9
+ return null;
10
+ return (0, connectionManager_1.getConnection)(ids[0]).client.db();
11
+ }
@@ -14,5 +14,6 @@ export declare function sanitizeIntegrationConnection<T extends object>(conn: T)
14
14
  export declare function sanitizeIntegrationConnections<T extends object>(conns: T[] | undefined): (T & {
15
15
  hasCredential: true;
16
16
  })[];
17
+ export declare function exportSummaries(exports: any[] | undefined): any[];
17
18
  export declare function sanitizeProject(project: any): any;
18
19
  export declare function sanitizeUserIntegration(integration: any): any;
@@ -4,6 +4,7 @@ exports.sanitizeConnection = sanitizeConnection;
4
4
  exports.sanitizeConnections = sanitizeConnections;
5
5
  exports.sanitizeIntegrationConnection = sanitizeIntegrationConnection;
6
6
  exports.sanitizeIntegrationConnections = sanitizeIntegrationConnections;
7
+ exports.exportSummaries = exportSummaries;
7
8
  exports.sanitizeProject = sanitizeProject;
8
9
  exports.sanitizeUserIntegration = sanitizeUserIntegration;
9
10
  function sanitizeConnection(conn) {
@@ -23,6 +24,19 @@ function sanitizeIntegrationConnection(conn) {
23
24
  function sanitizeIntegrationConnections(conns) {
24
25
  return (conns ?? []).map(sanitizeIntegrationConnection);
25
26
  }
27
+ // A project's exports as they're listed: what each one is, not its schema nor its credentials.
28
+ // The same in the project (GET /projects/:id) and in its own list (GET /projects/:id/exports).
29
+ function exportSummaries(exports) {
30
+ return (exports || []).map((e) => ({
31
+ id: e.id,
32
+ name: e.name,
33
+ description: e.description,
34
+ method: e.method,
35
+ private: e.private,
36
+ useConnections: e.useConnections,
37
+ useCredentials: e.useCredentials,
38
+ }));
39
+ }
26
40
  function sanitizeProject(project) {
27
41
  if (!project)
28
42
  return project;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",