streamby-core 0.39.0 → 0.41.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 (63) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/LICENSE +661 -661
  3. package/README.md +785 -573
  4. package/dist/adapters/database/connectionManager.js +182 -124
  5. package/dist/adapters/database/sql.js +1 -1
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +9 -1
  8. package/dist/mcp/audit.d.ts +5 -2
  9. package/dist/mcp/audit.js +8 -0
  10. package/dist/mcp/server.js +3 -3
  11. package/dist/mcp/tools.js +41 -0
  12. package/dist/middleware/createRouter.js +14 -1
  13. package/dist/middleware/routes/ai.js +3 -3
  14. package/dist/middleware/routes/deliverable.d.ts +4 -2
  15. package/dist/middleware/routes/deliverable.js +16 -7
  16. package/dist/middleware/routes/mcp.js +11 -4
  17. package/dist/middleware/routes/media.js +7 -6
  18. package/dist/middleware/routes/{apiConnection.d.ts → oauthResource.d.ts} +1 -1
  19. package/dist/middleware/routes/oauthResource.js +18 -0
  20. package/dist/middleware/routes/project.js +5 -1
  21. package/dist/middleware/routes/renderFarm.js +4 -77
  22. package/dist/middleware/routes/storage.js +13 -27
  23. package/dist/middleware/routes/worker.d.ts +7 -0
  24. package/dist/middleware/routes/worker.js +125 -0
  25. package/dist/middleware/routes/workerCallback.d.ts +5 -0
  26. package/dist/middleware/routes/workerCallback.js +31 -0
  27. package/dist/models/Model.js +9 -9
  28. package/dist/services/aiProcessor.js +11 -11
  29. package/dist/services/apiTokenPermissions.js +6 -5
  30. package/dist/services/auth.d.ts +2 -0
  31. package/dist/services/auth.js +36 -10
  32. package/dist/services/executor.js +10 -10
  33. package/dist/services/jobQueue.d.ts +6 -4
  34. package/dist/services/jobQueue.js +28 -16
  35. package/dist/services/jobStore.d.ts +27 -0
  36. package/dist/services/jobStore.js +177 -0
  37. package/dist/services/mediaProcessor.js +16 -16
  38. package/dist/services/oauthTokens.d.ts +60 -0
  39. package/dist/services/oauthTokens.js +164 -0
  40. package/dist/services/vfxProcessor.js +12 -12
  41. package/dist/services/workerJobs.d.ts +73 -0
  42. package/dist/services/workerJobs.js +459 -0
  43. package/dist/services/workers.d.ts +78 -0
  44. package/dist/services/workers.js +179 -0
  45. package/dist/types/index.d.ts +64 -2
  46. package/dist/utils/assetCategories.d.ts +4 -0
  47. package/dist/utils/assetCategories.js +21 -0
  48. package/dist/utils/builtinAccess.d.ts +1 -1
  49. package/dist/utils/builtinAccess.js +4 -3
  50. package/dist/utils/privateAddress.d.ts +10 -0
  51. package/dist/utils/privateAddress.js +85 -0
  52. package/package.json +1 -1
  53. package/dist/middleware/routes/apiConnection.js +0 -100
  54. package/dist/middleware/routes/credential.d.ts +0 -3
  55. package/dist/middleware/routes/credential.js +0 -80
  56. package/dist/services/apiConnection.d.ts +0 -18
  57. package/dist/services/apiConnection.js +0 -65
  58. package/dist/services/credential.d.ts +0 -8
  59. package/dist/services/credential.js +0 -70
  60. package/dist/services/pipeline.d.ts +0 -2
  61. package/dist/services/pipeline.js +0 -292
  62. package/dist/utils/crypto.d.ts +0 -3
  63. package/dist/utils/crypto.js +0 -32
@@ -45,7 +45,7 @@ function inferMetadata(contentType, size) {
45
45
  return meta;
46
46
  }
47
47
  async function runIngestJob(jobId, fileId, projectId) {
48
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10 });
48
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10 });
49
49
  try {
50
50
  const filesCol = getFilesCollection();
51
51
  if (!filesCol)
@@ -53,7 +53,7 @@ async function runIngestJob(jobId, fileId, projectId) {
53
53
  const fileDoc = await filesCol.findOne({ fileId, projectId });
54
54
  if (!fileDoc)
55
55
  throw new Error(`File ${fileId} not found in project ${projectId}`);
56
- (0, jobQueue_1.updateJob)(jobId, { stage: 'extracting-metadata', progress: 40, assetId: fileId });
56
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'extracting-metadata', progress: 40, assetId: fileId });
57
57
  const metaCol = getMetadataCollection();
58
58
  if (metaCol) {
59
59
  const meta = inferMetadata(fileDoc.contentType ?? '', fileDoc.size ?? 0);
@@ -67,7 +67,7 @@ async function runIngestJob(jobId, fileId, projectId) {
67
67
  },
68
68
  }, { upsert: true });
69
69
  }
70
- (0, jobQueue_1.updateJob)(jobId, { stage: 'creating-version', progress: 70 });
70
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'creating-version', progress: 70 });
71
71
  const versionsCol = getVersionsCollection();
72
72
  // Its upload already recorded the version it's in; only an asset from before versions
73
73
  // were kept gets one here.
@@ -85,14 +85,14 @@ async function runIngestJob(jobId, fileId, projectId) {
85
85
  createdAt: new Date(),
86
86
  });
87
87
  }
88
- (0, jobQueue_1.updateJob)(jobId, { status: 'completed', stage: 'complete', progress: 100 });
88
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'completed', stage: 'complete', progress: 100 });
89
89
  }
90
90
  catch (err) {
91
- (0, jobQueue_1.failJob)(jobId, err.message);
91
+ await (0, jobQueue_1.failJob)(jobId, err.message);
92
92
  }
93
93
  }
94
94
  async function runTranscodeJob(jobId, fileId, projectId, options, _adapter) {
95
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'queued', progress: 5, assetId: fileId });
95
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'queued', progress: 5, assetId: fileId });
96
96
  try {
97
97
  const filesCol = getFilesCollection();
98
98
  if (!filesCol)
@@ -103,7 +103,7 @@ async function runTranscodeJob(jobId, fileId, projectId, options, _adapter) {
103
103
  if (!fileDoc.contentType?.startsWith('video/') && !fileDoc.contentType?.startsWith('audio/')) {
104
104
  throw new Error('Transcode only supports video and audio files');
105
105
  }
106
- (0, jobQueue_1.updateJob)(jobId, { stage: 'transcoding', progress: 20 });
106
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'transcoding', progress: 20 });
107
107
  // Store the transcode request as a pending task — actual FFmpeg execution
108
108
  // happens in the host environment (Nhexa-API worker). The job record is the
109
109
  // handoff contract: the worker reads it, processes the file, and calls back
@@ -127,14 +127,14 @@ async function runTranscodeJob(jobId, fileId, projectId, options, _adapter) {
127
127
  },
128
128
  }, { upsert: true });
129
129
  }
130
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
130
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
131
131
  }
132
132
  catch (err) {
133
- (0, jobQueue_1.failJob)(jobId, err.message);
133
+ await (0, jobQueue_1.failJob)(jobId, err.message);
134
134
  }
135
135
  }
136
136
  async function runThumbnailJob(jobId, fileId, projectId, options) {
137
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
137
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
138
138
  try {
139
139
  const filesCol = getFilesCollection();
140
140
  if (!filesCol)
@@ -142,7 +142,7 @@ async function runThumbnailJob(jobId, fileId, projectId, options) {
142
142
  const fileDoc = await filesCol.findOne({ fileId, projectId });
143
143
  if (!fileDoc)
144
144
  throw new Error(`File ${fileId} not found`);
145
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
145
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
146
146
  const metaCol = getMetadataCollection();
147
147
  if (metaCol) {
148
148
  await metaCol.updateOne({ assetId: fileId, projectId }, {
@@ -160,14 +160,14 @@ async function runThumbnailJob(jobId, fileId, projectId, options) {
160
160
  },
161
161
  }, { upsert: true });
162
162
  }
163
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 40 });
163
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 40 });
164
164
  }
165
165
  catch (err) {
166
- (0, jobQueue_1.failJob)(jobId, err.message);
166
+ await (0, jobQueue_1.failJob)(jobId, err.message);
167
167
  }
168
168
  }
169
169
  async function runCaptionJob(jobId, fileId, projectId, options) {
170
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
170
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
171
171
  try {
172
172
  const filesCol = getFilesCollection();
173
173
  if (!filesCol)
@@ -195,9 +195,9 @@ async function runCaptionJob(jobId, fileId, projectId, options) {
195
195
  },
196
196
  }, { upsert: true });
197
197
  }
198
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
198
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
199
199
  }
200
200
  catch (err) {
201
- (0, jobQueue_1.failJob)(jobId, err.message);
201
+ await (0, jobQueue_1.failJob)(jobId, err.message);
202
202
  }
203
203
  }
@@ -0,0 +1,60 @@
1
+ import { ApiTokenScope, OAuthResourceConfig } from '../types';
2
+ import { ApiTokenPermission } from './apiTokenPermissions';
3
+ export declare const OAUTH_SCOPES: readonly ["streamby:read", "streamby:write"];
4
+ export type OAuthScope = typeof OAUTH_SCOPES[number];
5
+ /** The permissions each scope grants. */
6
+ export declare const OAUTH_SCOPE_PERMISSIONS: Record<OAuthScope, ApiTokenPermission[]>;
7
+ /** What a token's scopes let it do (scopes that aren't StreamBy's grant nothing here). */
8
+ export declare const permissionsForOAuthScopes: (scopes: string[]) => ApiTokenPermission[];
9
+ export interface Jwk {
10
+ kty: string;
11
+ kid?: string;
12
+ n?: string;
13
+ e?: string;
14
+ alg?: string;
15
+ use?: string;
16
+ }
17
+ export interface Jwks {
18
+ keys: Jwk[];
19
+ }
20
+ export interface OAuthTokenVerifierOptions {
21
+ issuer: string;
22
+ audiences: string[];
23
+ jwks: string | (() => Promise<Jwks>);
24
+ jwksCacheMs?: number;
25
+ fetcher?: (url: string, init: RequestInit) => Promise<Response>;
26
+ now?: () => number;
27
+ }
28
+ export interface VerifiedOAuthToken {
29
+ userId: string;
30
+ clientId: string;
31
+ scopes: string[];
32
+ expiresAt: Date;
33
+ scope: ApiTokenScope;
34
+ }
35
+ /**
36
+ * Whether a bearer is shaped as an OAuth access token (a JWT signed with RS256), not whether
37
+ * it's valid: for an authProvider to tell it from its own session tokens.
38
+ */
39
+ export declare const looksLikeOAuthAccessToken: (token: unknown) => token is string;
40
+ /** The grant an OAuth request counts as (its rate limit's key): one app, for one user. */
41
+ export declare const oauthGrantId: (clientId: string, userId: string) => string;
42
+ /**
43
+ * Checks OAuth access tokens: their signature with the issuer's keys, the issuer, that they're
44
+ * for StreamBy (their audience), and that they haven't expired. The verified token, or null.
45
+ * It doesn't know whether the user has since revoked the app: the host may check that too.
46
+ */
47
+ export declare function createOAuthTokenVerifier(options: OAuthTokenVerifierOptions): (token: unknown) => Promise<VerifiedOAuthToken | null>;
48
+ /** Where RFC 9728 puts a resource's metadata: at its origin, before its path. */
49
+ export declare const defaultResourceMetadataUrl: (resource: string) => string;
50
+ export declare const resourceMetadataUrlOf: (oauth: OAuthResourceConfig) => string;
51
+ /** The metadata a client reads to know where to sign in, and what to ask for. */
52
+ export declare const protectedResourceMetadata: (oauth: OAuthResourceConfig) => {
53
+ resource: string;
54
+ authorization_servers: string[];
55
+ scopes_supported: ("streamby:read" | "streamby:write")[];
56
+ bearer_methods_supported: string[];
57
+ resource_name: string;
58
+ };
59
+ /** The WWW-Authenticate challenge of a 401: the metadata's URL, and why the token was refused. */
60
+ export declare const wwwAuthenticate: (oauth: OAuthResourceConfig, invalidToken: boolean) => string;
@@ -0,0 +1,164 @@
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.wwwAuthenticate = exports.protectedResourceMetadata = exports.resourceMetadataUrlOf = exports.defaultResourceMetadataUrl = exports.oauthGrantId = exports.looksLikeOAuthAccessToken = exports.permissionsForOAuthScopes = exports.OAUTH_SCOPE_PERMISSIONS = exports.OAUTH_SCOPES = void 0;
7
+ exports.createOAuthTokenVerifier = createOAuthTokenVerifier;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ // ── OAuth access tokens ──
10
+ // An app the user signed in to with their account (e.g. claude.ai's connector) comes with an
11
+ // OAuth access token: a JWT the authorization server signed (RS256) for one resource (its
12
+ // audience, RFC 8707). It's checked with the server's published keys alone (JWKS): no secret
13
+ // is shared. What it may do is what the user approved -- its scopes -- as permissions of the
14
+ // API tokens' kind, so the same checks limit it; and, as a user's token, never more than the
15
+ // user can (their role in each project still applies).
16
+ exports.OAUTH_SCOPES = ['streamby:read', 'streamby:write'];
17
+ const READ_PERMISSIONS = [
18
+ 'project:read', 'members:read', 'assets:read', 'production:read', 'workflow:read',
19
+ 'exports:read', 'rights:read', 'jobs:read',
20
+ ];
21
+ // Making and changing, as the consent screen says: never deleting, nor managing members.
22
+ const WRITE_PERMISSIONS = [
23
+ 'project:update', 'assets:write', 'production:write', 'workflow:write', 'exports:write',
24
+ 'rights:write', 'jobs:run', 'projects:create',
25
+ ];
26
+ /** The permissions each scope grants. */
27
+ exports.OAUTH_SCOPE_PERMISSIONS = {
28
+ 'streamby:read': READ_PERMISSIONS,
29
+ 'streamby:write': [...READ_PERMISSIONS, ...WRITE_PERMISSIONS],
30
+ };
31
+ /** What a token's scopes let it do (scopes that aren't StreamBy's grant nothing here). */
32
+ const permissionsForOAuthScopes = (scopes) => [...new Set(scopes.flatMap(scope => exports.OAUTH_SCOPE_PERMISSIONS[scope] ?? []))];
33
+ exports.permissionsForOAuthScopes = permissionsForOAuthScopes;
34
+ const SKEW_SECONDS = 60;
35
+ const REFETCH_MIN_MS = 60 * 1000;
36
+ const decodePart = (part) => {
37
+ try {
38
+ const value = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
39
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ };
45
+ /**
46
+ * Whether a bearer is shaped as an OAuth access token (a JWT signed with RS256), not whether
47
+ * it's valid: for an authProvider to tell it from its own session tokens.
48
+ */
49
+ const looksLikeOAuthAccessToken = (token) => {
50
+ if (typeof token !== 'string')
51
+ return false;
52
+ const parts = token.split('.');
53
+ return parts.length === 3 && decodePart(parts[0])?.alg === 'RS256';
54
+ };
55
+ exports.looksLikeOAuthAccessToken = looksLikeOAuthAccessToken;
56
+ /** The grant an OAuth request counts as (its rate limit's key): one app, for one user. */
57
+ const oauthGrantId = (clientId, userId) => `oauth:${clientId}:${userId}`;
58
+ exports.oauthGrantId = oauthGrantId;
59
+ /**
60
+ * Checks OAuth access tokens: their signature with the issuer's keys, the issuer, that they're
61
+ * for StreamBy (their audience), and that they haven't expired. The verified token, or null.
62
+ * It doesn't know whether the user has since revoked the app: the host may check that too.
63
+ */
64
+ function createOAuthTokenVerifier(options) {
65
+ const now = () => (options.now ? options.now() : Date.now());
66
+ const cacheMs = options.jwksCacheMs ?? 5 * 60 * 1000;
67
+ const fetcher = options.fetcher ?? fetch;
68
+ let keys = new Map();
69
+ let fetchedAt = -Infinity;
70
+ let inFlight = null;
71
+ const loadKeys = async () => {
72
+ const jwks = typeof options.jwks === 'function'
73
+ ? await options.jwks()
74
+ : await fetcher(options.jwks, { headers: { Accept: 'application/json' }, redirect: 'error', signal: AbortSignal.timeout(5000) })
75
+ .then(res => (res.ok ? res.json() : Promise.reject(new Error(`JWKS answered ${res.status}`))));
76
+ const next = new Map();
77
+ for (const jwk of jwks?.keys ?? []) {
78
+ if (jwk.kty !== 'RSA' || !jwk.kid || (jwk.alg && jwk.alg !== 'RS256') || (jwk.use && jwk.use !== 'sig'))
79
+ continue;
80
+ try {
81
+ next.set(jwk.kid, crypto_1.default.createPublicKey({ key: jwk, format: 'jwk' }));
82
+ }
83
+ catch { /* not a usable key */ }
84
+ }
85
+ keys = next;
86
+ fetchedAt = now();
87
+ };
88
+ const refresh = async () => {
89
+ inFlight ?? (inFlight = loadKeys().catch(err => {
90
+ fetchedAt = now(); // don't hammer a failing JWKS
91
+ console.error('❌ StreamBy OAuth: could not load the issuer\'s keys:', err instanceof Error ? err.message : err);
92
+ }).finally(() => { inFlight = null; }));
93
+ await inFlight;
94
+ };
95
+ const keyFor = async (kid) => {
96
+ if (now() - fetchedAt > cacheMs)
97
+ await refresh();
98
+ if (!keys.has(kid) && now() - fetchedAt > REFETCH_MIN_MS)
99
+ await refresh();
100
+ return keys.get(kid);
101
+ };
102
+ return async function verify(token) {
103
+ if (!(0, exports.looksLikeOAuthAccessToken)(token))
104
+ return null;
105
+ const [headerPart, payloadPart, signaturePart] = token.split('.');
106
+ const header = decodePart(headerPart);
107
+ const claims = decodePart(payloadPart);
108
+ if (!header || !claims || typeof header.kid !== 'string')
109
+ return null;
110
+ const key = await keyFor(header.kid);
111
+ if (!key)
112
+ return null;
113
+ const signed = crypto_1.default.verify('RSA-SHA256', Buffer.from(`${headerPart}.${payloadPart}`), key, Buffer.from(signaturePart, 'base64url'));
114
+ if (!signed)
115
+ return null;
116
+ const seconds = Math.floor(now() / 1000);
117
+ const audiences = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
118
+ if (claims.iss !== options.issuer)
119
+ return null;
120
+ if (!audiences.some(aud => typeof aud === 'string' && options.audiences.includes(aud)))
121
+ return null;
122
+ if (typeof claims.exp !== 'number' || claims.exp + SKEW_SECONDS <= seconds)
123
+ return null;
124
+ if (typeof claims.nbf === 'number' && claims.nbf - SKEW_SECONDS > seconds)
125
+ return null;
126
+ if (typeof claims.sub !== 'string' || !claims.sub || typeof claims.client_id !== 'string' || !claims.client_id)
127
+ return null;
128
+ const scopes = typeof claims.scope === 'string' ? claims.scope.split(' ').filter(Boolean) : [];
129
+ return {
130
+ userId: claims.sub,
131
+ clientId: claims.client_id,
132
+ scopes,
133
+ expiresAt: new Date(claims.exp * 1000),
134
+ scope: {
135
+ tokenId: (0, exports.oauthGrantId)(claims.client_id, claims.sub),
136
+ kind: 'user',
137
+ permissions: (0, exports.permissionsForOAuthScopes)(scopes),
138
+ projectIds: '*',
139
+ },
140
+ };
141
+ };
142
+ }
143
+ // ── The protected resource's metadata (RFC 9728) ──
144
+ /** Where RFC 9728 puts a resource's metadata: at its origin, before its path. */
145
+ const defaultResourceMetadataUrl = (resource) => {
146
+ const url = new URL(resource);
147
+ const path = url.pathname === '/' ? '' : url.pathname.replace(/\/$/, '');
148
+ return `${url.origin}/.well-known/oauth-protected-resource${path}`;
149
+ };
150
+ exports.defaultResourceMetadataUrl = defaultResourceMetadataUrl;
151
+ const resourceMetadataUrlOf = (oauth) => oauth.resourceMetadataUrl ?? (0, exports.defaultResourceMetadataUrl)(oauth.resource);
152
+ exports.resourceMetadataUrlOf = resourceMetadataUrlOf;
153
+ /** The metadata a client reads to know where to sign in, and what to ask for. */
154
+ const protectedResourceMetadata = (oauth) => ({
155
+ resource: oauth.resource,
156
+ authorization_servers: [oauth.issuer],
157
+ scopes_supported: [...exports.OAUTH_SCOPES],
158
+ bearer_methods_supported: ['header'],
159
+ resource_name: oauth.resourceName ?? 'StreamBy',
160
+ });
161
+ exports.protectedResourceMetadata = protectedResourceMetadata;
162
+ /** The WWW-Authenticate challenge of a 401: the metadata's URL, and why the token was refused. */
163
+ const wwwAuthenticate = (oauth, invalidToken) => `Bearer resource_metadata="${(0, exports.resourceMetadataUrlOf)(oauth)}"${invalidToken ? ', error="invalid_token"' : ''}`;
164
+ exports.wwwAuthenticate = wwwAuthenticate;
@@ -13,7 +13,7 @@ function getNosqlDb() {
13
13
  return (0, connectionManager_1.getConnection)(ids[0]).client.db();
14
14
  }
15
15
  async function runRenderJob(jobId, fileId, projectId, options) {
16
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
16
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
17
17
  try {
18
18
  const db = getNosqlDb();
19
19
  if (!db)
@@ -21,7 +21,7 @@ async function runRenderJob(jobId, fileId, projectId, options) {
21
21
  const fileDoc = await db.collection('storage_files').findOne({ fileId, projectId });
22
22
  if (!fileDoc)
23
23
  throw new Error(`File ${fileId} not found`);
24
- (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-farm', progress: 20 });
24
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-farm', progress: 20 });
25
25
  // Store pending render task — picked up by the host render farm worker
26
26
  await db.collection('media_metadata').updateOne({ assetId: fileId, projectId }, {
27
27
  $set: {
@@ -40,14 +40,14 @@ async function runRenderJob(jobId, fileId, projectId, options) {
40
40
  extractedAt: new Date(),
41
41
  },
42
42
  }, { upsert: true });
43
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
43
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
44
44
  }
45
45
  catch (err) {
46
- (0, jobQueue_1.failJob)(jobId, err.message);
46
+ await (0, jobQueue_1.failJob)(jobId, err.message);
47
47
  }
48
48
  }
49
49
  async function runFormatConvertJob(jobId, fileId, projectId, options) {
50
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
50
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
51
51
  try {
52
52
  const db = getNosqlDb();
53
53
  if (!db)
@@ -55,7 +55,7 @@ async function runFormatConvertJob(jobId, fileId, projectId, options) {
55
55
  const fileDoc = await db.collection('storage_files').findOne({ fileId, projectId });
56
56
  if (!fileDoc)
57
57
  throw new Error(`File ${fileId} not found`);
58
- (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-convert', progress: 25 });
58
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-convert', progress: 25 });
59
59
  await db.collection('media_metadata').updateOne({ assetId: fileId, projectId }, {
60
60
  $set: {
61
61
  assetId: fileId,
@@ -71,14 +71,14 @@ async function runFormatConvertJob(jobId, fileId, projectId, options) {
71
71
  extractedAt: new Date(),
72
72
  },
73
73
  }, { upsert: true });
74
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 35 });
74
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 35 });
75
75
  }
76
76
  catch (err) {
77
- (0, jobQueue_1.failJob)(jobId, err.message);
77
+ await (0, jobQueue_1.failJob)(jobId, err.message);
78
78
  }
79
79
  }
80
80
  async function runLodJob(jobId, fileId, projectId, options) {
81
- (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
81
+ await (0, jobQueue_1.updateJob)(jobId, { status: 'processing', stage: 'resolving', progress: 10, assetId: fileId });
82
82
  try {
83
83
  const db = getNosqlDb();
84
84
  if (!db)
@@ -86,7 +86,7 @@ async function runLodJob(jobId, fileId, projectId, options) {
86
86
  const fileDoc = await db.collection('storage_files').findOne({ fileId, projectId });
87
87
  if (!fileDoc)
88
88
  throw new Error(`File ${fileId} not found`);
89
- (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-lod', progress: 25 });
89
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'queued-lod', progress: 25 });
90
90
  await db.collection('media_metadata').updateOne({ assetId: fileId, projectId }, {
91
91
  $set: {
92
92
  assetId: fileId,
@@ -102,10 +102,10 @@ async function runLodJob(jobId, fileId, projectId, options) {
102
102
  extractedAt: new Date(),
103
103
  },
104
104
  }, { upsert: true });
105
- (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
105
+ await (0, jobQueue_1.updateJob)(jobId, { stage: 'pending-worker', progress: 30 });
106
106
  }
107
107
  catch (err) {
108
- (0, jobQueue_1.failJob)(jobId, err.message);
108
+ await (0, jobQueue_1.failJob)(jobId, err.message);
109
109
  }
110
110
  }
111
111
  async function resolveAssetDependencyGraph(projectId, rootAssetId, maxDepth = 5) {
@@ -0,0 +1,73 @@
1
+ import { Auth, JobRecord, StorageAdapter, StreamByConfig, WorkerRef, WorkerResultMode } from '../types';
2
+ export declare const WORKER_CALLBACK_PATH = "/worker-jobs";
3
+ type RouterConfig = StreamByConfig & {
4
+ adapter?: StorageAdapter;
5
+ };
6
+ export interface WorkerJobRequest {
7
+ assetId: unknown;
8
+ versionId?: unknown;
9
+ worker: unknown;
10
+ kind: unknown;
11
+ profile?: unknown;
12
+ resultMode?: unknown;
13
+ }
14
+ /** Asks a worker to process an asset, as `auth`: the job, pending, to be sent right away. */
15
+ export declare function enqueueWorkerJob(project: any, request: WorkerJobRequest, auth: Auth, config: RouterConfig, callbackBase: string): Promise<JobRecord>;
16
+ /** Runs the workers set to process, by themselves, an asset of their categories marked as a deliverable. */
17
+ export declare function enqueueAutomaticJobs(project: any, assetId: string, versionId: string | null, auth: Auth, config: RouterConfig, callbackBase: string): Promise<JobRecord[]>;
18
+ /** A failed worker job, back in line. */
19
+ export declare function retryWorkerJob(jobId: string, config: RouterConfig): Promise<JobRecord | null>;
20
+ /**
21
+ * Sends a pending job (or one its worker didn't finish in time) to its worker. Of several
22
+ * instances, only the one that claims it does.
23
+ */
24
+ export declare function dispatchJob(jobId: string, config: RouterConfig): Promise<void>;
25
+ /** Looks, now and then, for jobs to send (again). Every instance does; claiming keeps them from doubling. */
26
+ export declare function startWorkerSweeper(config: RouterConfig): () => void;
27
+ /** The job a callback is about, if its token is this attempt's and the job is in progress. */
28
+ export declare function authorizeCallback(jobId: string, authorization: string | undefined): Promise<JobRecord>;
29
+ export declare function reportProgress(job: JobRecord, body: any): Promise<JobRecord | null>;
30
+ /** Where the worker uploads its result: a key of its own until the job completes. */
31
+ export declare function resultUploadUrl(job: JobRecord, body: any, config: RouterConfig): Promise<{
32
+ url: string;
33
+ method: string;
34
+ headers: {
35
+ 'Content-Type': any;
36
+ };
37
+ expiresAt: string;
38
+ }>;
39
+ /** The worker's result, kept: a new version of the asset, or a new asset derived from it. */
40
+ export declare function completeJob(job: JobRecord, body: any, config: RouterConfig): Promise<JobRecord>;
41
+ /** The worker couldn't do it: sent again if it says it may work later (and attempts are left), or failed. */
42
+ export declare function failWorkerJob(job: JobRecord, body: any, config: RouterConfig): Promise<JobRecord | null>;
43
+ /** What's shown of a job: never its callback token's hash, nor its upload's key. */
44
+ export declare const publicJob: ({ callbackHash: _hash, upload: _upload, payload, ...job }: JobRecord) => {
45
+ payload: Record<string, any>;
46
+ jobId: string;
47
+ jobType: import("../types").JobType;
48
+ userId: string;
49
+ projectId: string;
50
+ assetId?: string;
51
+ status: import("../types").JobStatus;
52
+ stage: string;
53
+ progress: number;
54
+ error?: string;
55
+ createdAt: Date;
56
+ updatedAt: Date;
57
+ worker?: WorkerRef | null;
58
+ kind?: string | null;
59
+ profile?: Record<string, unknown> | null;
60
+ versionId?: string | null;
61
+ resultMode?: WorkerResultMode | null;
62
+ attempts?: number;
63
+ maxAttempts?: number;
64
+ nextAttemptAt?: Date | null;
65
+ leaseUntil?: Date | null;
66
+ report?: Record<string, unknown> | null;
67
+ result?: {
68
+ assetId: string;
69
+ versionId: string;
70
+ mode: WorkerResultMode;
71
+ } | null;
72
+ };
73
+ export {};