feat: add image derivative worker lifecycle
Add worker-owned image derivative job execution with explicit PROCESSING, COMPLETED, and FAILED outcomes, plus tests and documentation. Verification: npm test passed 70 tests, worker tests passed 2 tests, and integration passed 14 tests. No build script is configured.
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
export async function runImageDerivativeJob({ job, loadSource, processor }) {
|
||||||
|
if (!job || job.type !== 'IMAGE_DERIVATIVES') {
|
||||||
|
throw new Error('Unsupported image derivative job.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const processingJob = { ...job, status: 'PROCESSING', error: null };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const source = await loadSource(job.sourceKey);
|
||||||
|
const derivatives = await processor.process(source);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...processingJob,
|
||||||
|
status: 'COMPLETED',
|
||||||
|
derivatives,
|
||||||
|
completedAt: new Date(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...processingJob,
|
||||||
|
status: 'FAILED',
|
||||||
|
error: error instanceof Error ? error.message : 'Image derivative processing failed.',
|
||||||
|
failedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import { runImageDerivativeJob } from '../src/image-derivative-worker.js';
|
||||||
|
|
||||||
|
test('completes an image derivative job after loading and processing the source', async () => {
|
||||||
|
const calls = [];
|
||||||
|
const result = await runImageDerivativeJob({
|
||||||
|
job: { id: 'job-1', type: 'IMAGE_DERIVATIVES', sourceKey: 'source.jpg', status: 'PENDING' },
|
||||||
|
async loadSource(sourceKey) {
|
||||||
|
calls.push(`load:${sourceKey}`);
|
||||||
|
return { sourceKey, data: Buffer.from('image') };
|
||||||
|
},
|
||||||
|
processor: {
|
||||||
|
async process(source) {
|
||||||
|
calls.push(`process:${source.sourceKey}`);
|
||||||
|
return [{ name: 'thumbnail', key: 'source-thumbnail.webp' }];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.status, 'COMPLETED');
|
||||||
|
assert.deepEqual(result.derivatives, [{ name: 'thumbnail', key: 'source-thumbnail.webp' }]);
|
||||||
|
assert.deepEqual(calls, ['load:source.jpg', 'process:source.jpg']);
|
||||||
|
assert.ok(result.completedAt instanceof Date);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('records a failed image derivative job without throwing worker errors', async () => {
|
||||||
|
const result = await runImageDerivativeJob({
|
||||||
|
job: { id: 'job-2', type: 'IMAGE_DERIVATIVES', sourceKey: 'missing.jpg', status: 'PENDING' },
|
||||||
|
async loadSource() {
|
||||||
|
throw new Error('source unavailable');
|
||||||
|
},
|
||||||
|
processor: { async process() { return []; } },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.status, 'FAILED');
|
||||||
|
assert.equal(result.error, 'source unavailable');
|
||||||
|
assert.ok(result.failedAt instanceof Date);
|
||||||
|
});
|
||||||
@@ -73,6 +73,10 @@ variants and writes them through `ImageStorage` using an injected renderer.
|
|||||||
The concrete decoder/encoder and worker queue remain separate infrastructure
|
The concrete decoder/encoder and worker queue remain separate infrastructure
|
||||||
work so local development does not depend on a machine-specific binary.
|
work so local development does not depend on a machine-specific binary.
|
||||||
|
|
||||||
|
The worker now owns the derivative job lifecycle and reports explicit
|
||||||
|
`PENDING`, `PROCESSING`, `COMPLETED`, or `FAILED` outcomes. Queue transport and
|
||||||
|
persistent job records remain deployment-specific work.
|
||||||
|
|
||||||
## Metadata
|
## Metadata
|
||||||
|
|
||||||
Store:
|
Store:
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ docker context use orbstack
|
|||||||
- Item-owned image metadata can be added through a seller-scoped, CSRF-protected admin route; publication uses the persisted image count.
|
- Item-owned image metadata can be added through a seller-scoped, CSRF-protected admin route; publication uses the persisted image count.
|
||||||
- `ImageStorage`, traversal-safe local storage, multipart upload transport, and binary signature validation are implemented; derivative processing remains.
|
- `ImageStorage`, traversal-safe local storage, multipart upload transport, and binary signature validation are implemented; derivative processing remains.
|
||||||
- Derivative orchestration now defines thumbnail, medium, and large WebP storage outputs behind an injected renderer; the concrete worker-backed renderer remains.
|
- Derivative orchestration now defines thumbnail, medium, and large WebP storage outputs behind an injected renderer; the concrete worker-backed renderer remains.
|
||||||
|
- The worker owns an image derivative job runner with explicit processing, completion, and failure outcomes; queue transport and persistent job records remain.
|
||||||
- Image metadata limits are enforced in the service boundary: supported MIME types, 10 MiB default file size, and 10,000-pixel maximum dimensions.
|
- Image metadata limits are enforced in the service boundary: supported MIME types, 10 MiB default file size, and 10,000-pixel maximum dimensions.
|
||||||
|
|
||||||
### Verified Baseline
|
### Verified Baseline
|
||||||
@@ -103,7 +104,7 @@ docker context use orbstack
|
|||||||
| HTTP | Product and inventory creation, validation, authentication/recovery, CSRF, rate limiting, and session management |
|
| HTTP | Product and inventory creation, validation, authentication/recovery, CSRF, rate limiting, and session management |
|
||||||
| Integration | PostgreSQL catalog/session/audit/password recovery persistence and Redis-shared login throttling |
|
| Integration | PostgreSQL catalog/session/audit/password recovery persistence and Redis-shared login throttling |
|
||||||
|
|
||||||
Latest result: `npm test` passed 70 tests; `npm run test:integration` passed 14 tests against OrbStack PostgreSQL and Redis.
|
Latest result: `npm test` passed 70 tests; worker tests passed 2 tests; `npm run test:integration` passed 14 tests against OrbStack PostgreSQL and Redis.
|
||||||
|
|
||||||
## Next Milestone
|
## Next Milestone
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ Work through one bounded vertical slice at a time. The linked documentation rema
|
|||||||
|
|
||||||
- No initial User/Seller is provisioned in the development database yet. Run `npm run provision:admin` after setting its required local `.env` variables.
|
- No initial User/Seller is provisioned in the development database yet. Run `npm run provision:admin` after setting its required local `.env` variables.
|
||||||
- Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
|
- Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
|
||||||
- Sales, acquisitions, pricing, image processing, search, worker jobs, web UI, CI, and deployment services remain unimplemented.
|
- Sales, acquisitions, pricing, concrete image rendering, search, queue transport, persistent worker jobs, web UI, CI, and deployment services remain unimplemented.
|
||||||
- `npm install` currently reports dependency audit findings. Review and remediate them before production deployment; do not use `npm audit fix --force` without reviewing its changes.
|
- `npm install` currently reports dependency audit findings. Review and remediate them before production deployment; do not use `npm audit fix --force` without reviewing its changes.
|
||||||
|
|
||||||
## Local Commands
|
## Local Commands
|
||||||
|
|||||||
Reference in New Issue
Block a user