feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
import { defineHook } from '@directus/extensions-sdk';
|
2026-10-25 14:26:49 +03:00
|
|
|
import { runBackfill } from '../lib/backfill';
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
import { recordOp } from '../lib/diagnostics';
|
2026-10-30 10:22:29 +03:00
|
|
|
import { replayFailureMap } from '../lib/failures';
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
import { buildRegistry, isImageType, loadCustomPresets } from '../lib/presets';
|
2026-10-30 10:22:29 +03:00
|
|
|
import { journalFailure, markRecovered, runDueRetries } from '../lib/retry';
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
import { readSettings } from '../lib/settings';
|
2026-10-25 14:26:49 +03:00
|
|
|
import { readRun } from '../lib/state';
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
import { warmFile } from '../lib/warmer';
|
2026-10-25 14:26:49 +03:00
|
|
|
import { isInWindow } from '../lib/window';
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
|
|
|
|
|
interface UploadMeta {
|
|
|
|
|
key?: string;
|
|
|
|
|
collection?: string;
|
|
|
|
|
payload?: { type?: string | null; collection?: string };
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-25 14:26:49 +03:00
|
|
|
export default defineHook(({ action, schedule }, { services, database, getSchema, env, logger }) => {
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
action('files.upload', (meta) => {
|
|
|
|
|
void warmUpload(meta as UploadMeta).catch((error) =>
|
|
|
|
|
logger.error(error, 'image-previews: upload warm failed'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-25 14:26:49 +03:00
|
|
|
schedule('*/15 * * * *', () => {
|
|
|
|
|
void scheduledBackfill().catch((error) =>
|
|
|
|
|
logger.error(error, 'image-previews: scheduled run failed'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
async function warmUpload(meta: UploadMeta): Promise<void> {
|
|
|
|
|
const settings = await readSettings(env);
|
|
|
|
|
|
|
|
|
|
if (!settings.enabled || !settings.onUpload) return;
|
|
|
|
|
|
|
|
|
|
const fileId = meta?.key;
|
|
|
|
|
const payload = meta?.payload;
|
|
|
|
|
|
|
|
|
|
if (!fileId || !payload) return;
|
|
|
|
|
if (payload.collection && payload.collection !== 'directus_files') return;
|
|
|
|
|
if (!isImageType(payload.type)) return;
|
|
|
|
|
|
|
|
|
|
const schema = await getSchema();
|
|
|
|
|
const custom = await loadCustomPresets(new services['SettingsService']({ schema }));
|
|
|
|
|
const presets = buildRegistry(custom, settings.presetKeys);
|
|
|
|
|
|
|
|
|
|
if (presets.length === 0) return;
|
|
|
|
|
|
|
|
|
|
const assets = new services['AssetsService']({ schema });
|
|
|
|
|
const summary = await warmFile(assets, fileId, presets, {
|
|
|
|
|
concurrency: settings.concurrency,
|
|
|
|
|
formats: settings.formats,
|
|
|
|
|
onResult: (result) =>
|
|
|
|
|
recordOp({
|
|
|
|
|
ts: Date.now(),
|
|
|
|
|
fileId,
|
|
|
|
|
key: result.key,
|
|
|
|
|
format: result.format,
|
|
|
|
|
ok: result.ok,
|
|
|
|
|
ms: result.ms,
|
|
|
|
|
error: result.error,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-30 10:22:29 +03:00
|
|
|
const failureRecords = await replayFailureMap(env);
|
|
|
|
|
|
|
|
|
|
for (const result of summary.results) {
|
|
|
|
|
if (result.ok) {
|
|
|
|
|
await markRecovered(
|
|
|
|
|
env,
|
|
|
|
|
{
|
|
|
|
|
file_id: fileId,
|
|
|
|
|
preset_key: result.key,
|
|
|
|
|
format: result.format,
|
|
|
|
|
ms: result.ms,
|
|
|
|
|
},
|
|
|
|
|
{ records: failureRecords, logger },
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
await journalFailure(
|
|
|
|
|
env,
|
|
|
|
|
{
|
|
|
|
|
file_id: fileId,
|
|
|
|
|
preset_key: result.key,
|
|
|
|
|
format: result.format,
|
|
|
|
|
error: result.error,
|
|
|
|
|
ms: result.ms,
|
|
|
|
|
},
|
|
|
|
|
{ records: failureRecords, logger },
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
if (settings.debug) logger.info({ fileId, summary }, 'image-previews: warmed upload');
|
|
|
|
|
}
|
2026-10-25 14:26:49 +03:00
|
|
|
|
|
|
|
|
async function scheduledBackfill(): Promise<void> {
|
|
|
|
|
const settings = await readSettings(env);
|
|
|
|
|
|
|
|
|
|
if (!settings.enabled) return;
|
|
|
|
|
if (!isInWindow(settings.windowStart, settings.windowEnd)) return;
|
|
|
|
|
|
|
|
|
|
const run = await readRun(env);
|
|
|
|
|
|
|
|
|
|
if (run?.running) return;
|
|
|
|
|
|
2026-10-30 10:22:29 +03:00
|
|
|
const retryOutcome = await runDueRetries({ services, database, getSchema, env, logger }, {});
|
|
|
|
|
|
|
|
|
|
if (retryOutcome.status === 'completed' && retryOutcome.processed > 0) {
|
|
|
|
|
logger.info(retryOutcome, 'image-previews: due retries processed');
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-25 14:26:49 +03:00
|
|
|
const finishedToday = run?.finishedAt
|
|
|
|
|
? new Date(run.finishedAt).toDateString() === new Date().toDateString()
|
|
|
|
|
: false;
|
|
|
|
|
|
|
|
|
|
if (finishedToday && run && run.done >= run.total) return;
|
|
|
|
|
|
|
|
|
|
logger.info('image-previews: scheduled backfill starting');
|
|
|
|
|
|
|
|
|
|
void runBackfill({ services, database, getSchema, env, logger }, {}).catch((error) =>
|
|
|
|
|
logger.error(error, 'image-previews: scheduled backfill failed'),
|
|
|
|
|
);
|
|
|
|
|
}
|
feat: pre-generate Directus image previews
Bundle extension (upload hook + API endpoint + admin module) that warms
Directus image transform cache in-process with an explicit acceptFormat,
so the generated file matches the browser's cache key byte-for-byte.
- resumable, window-aware backfill with progress + cancel
- system + custom presets, effective-format reporting, shadow warnings
- storage breakdown (previews vs originals), human-readable API fields
- diagnostics: resolve-plan prediction, per-file generate, ops ring buffer
- isolated dev/staging compose stack + deploy/rollback scripts
Verified on Directus 11.17.4: 40/40 tests, hash-match gate passed,
idempotent backfill, custom fixed-format presets, error surfacing.
2026-10-25 11:19:54 +03:00
|
|
|
});
|