@prenta/core 8.9.2 → 8.11.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 +105 -0
- package/dist/actions.d.ts +25 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +51 -4
- package/dist/actions.js.map +1 -1
- package/dist/ai/batch-links.d.ts +105 -0
- package/dist/ai/batch-links.d.ts.map +1 -0
- package/dist/ai/batch-links.js +667 -0
- package/dist/ai/batch-links.js.map +1 -0
- package/dist/ai/batch-list.d.ts +127 -4
- package/dist/ai/batch-list.d.ts.map +1 -1
- package/dist/ai/batch-list.js +250 -14
- package/dist/ai/batch-list.js.map +1 -1
- package/dist/ai/providers/http.d.ts +4 -0
- package/dist/ai/providers/http.d.ts.map +1 -1
- package/dist/ai/providers/http.js +9 -8
- package/dist/ai/providers/http.js.map +1 -1
- package/dist/ai/tasks/editor-text.d.ts +39 -0
- package/dist/ai/tasks/editor-text.d.ts.map +1 -0
- package/dist/ai/tasks/editor-text.js +45 -0
- package/dist/ai/tasks/editor-text.js.map +1 -0
- package/dist/ai/tasks/link-embeddings.d.ts +22 -0
- package/dist/ai/tasks/link-embeddings.d.ts.map +1 -1
- package/dist/ai/tasks/link-embeddings.js +24 -10
- package/dist/ai/tasks/link-embeddings.js.map +1 -1
- package/dist/ai/usage.d.ts +7 -0
- package/dist/ai/usage.d.ts.map +1 -1
- package/dist/ai/usage.js +11 -0
- package/dist/ai/usage.js.map +1 -1
- package/dist/api/guarded-db.d.ts +6 -0
- package/dist/api/guarded-db.d.ts.map +1 -1
- package/dist/api/guarded-db.js +13 -1
- package/dist/api/guarded-db.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +2 -0
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/openapi.d.ts.map +1 -1
- package/dist/api/openapi.js +103 -0
- package/dist/api/openapi.js.map +1 -1
- package/dist/api/route-helpers/forms.d.ts.map +1 -1
- package/dist/api/route-helpers/forms.js +7 -1
- package/dist/api/route-helpers/forms.js.map +1 -1
- package/dist/api/routes/ai-authoring.d.ts.map +1 -1
- package/dist/api/routes/ai-authoring.js +1 -0
- package/dist/api/routes/ai-authoring.js.map +1 -1
- package/dist/api/routes/ai-seo.d.ts.map +1 -1
- package/dist/api/routes/ai-seo.js +20 -18
- package/dist/api/routes/ai-seo.js.map +1 -1
- package/dist/api/routes/media.d.ts.map +1 -1
- package/dist/api/routes/media.js +3 -1
- package/dist/api/routes/media.js.map +1 -1
- package/dist/api/routes/scheduling.d.ts.map +1 -1
- package/dist/api/routes/scheduling.js +33 -2
- package/dist/api/routes/scheduling.js.map +1 -1
- package/dist/appearance/color.d.ts +17 -0
- package/dist/appearance/color.d.ts.map +1 -1
- package/dist/appearance/color.js +162 -1
- package/dist/appearance/color.js.map +1 -1
- package/dist/appearance/index.d.ts +3 -3
- package/dist/appearance/index.d.ts.map +1 -1
- package/dist/appearance/index.js +2 -2
- package/dist/appearance/index.js.map +1 -1
- package/dist/appearance/resolve.d.ts.map +1 -1
- package/dist/appearance/resolve.js +4 -2
- package/dist/appearance/resolve.js.map +1 -1
- package/dist/appearance/theme-tokens.d.ts +60 -11
- package/dist/appearance/theme-tokens.d.ts.map +1 -1
- package/dist/appearance/theme-tokens.js +184 -175
- package/dist/appearance/theme-tokens.js.map +1 -1
- package/dist/appearance/types.d.ts +16 -2
- package/dist/appearance/types.d.ts.map +1 -1
- package/dist/astro-site.d.ts +1 -11
- package/dist/astro-site.d.ts.map +1 -1
- package/dist/astro-site.js +4 -0
- package/dist/astro-site.js.map +1 -1
- package/dist/auth/team.d.ts +1 -0
- package/dist/auth/team.d.ts.map +1 -1
- package/dist/auth/team.js +1 -1
- package/dist/auth/team.js.map +1 -1
- package/dist/cache/http.d.ts +20 -0
- package/dist/cache/http.d.ts.map +1 -1
- package/dist/cache/http.js +42 -0
- package/dist/cache/http.js.map +1 -1
- package/dist/chrome/announcement.d.ts +30 -0
- package/dist/chrome/announcement.d.ts.map +1 -0
- package/dist/chrome/announcement.js +190 -0
- package/dist/chrome/announcement.js.map +1 -0
- package/dist/chrome/codegen.d.ts +17 -0
- package/dist/chrome/codegen.d.ts.map +1 -0
- package/dist/chrome/codegen.js +96 -0
- package/dist/chrome/codegen.js.map +1 -0
- package/dist/chrome/contract.d.ts +161 -0
- package/dist/chrome/contract.d.ts.map +1 -0
- package/dist/chrome/contract.js +2 -0
- package/dist/chrome/contract.js.map +1 -0
- package/dist/chrome/memory-store.d.ts +15 -0
- package/dist/chrome/memory-store.d.ts.map +1 -0
- package/dist/chrome/memory-store.js +54 -0
- package/dist/chrome/memory-store.js.map +1 -0
- package/dist/chrome/overrides.d.ts +25 -0
- package/dist/chrome/overrides.d.ts.map +1 -0
- package/dist/chrome/overrides.js +180 -0
- package/dist/chrome/overrides.js.map +1 -0
- package/dist/chrome/parse.d.ts +36 -0
- package/dist/chrome/parse.d.ts.map +1 -0
- package/dist/chrome/parse.js +277 -0
- package/dist/chrome/parse.js.map +1 -0
- package/dist/chrome/prisma-store.d.ts +11 -0
- package/dist/chrome/prisma-store.d.ts.map +1 -0
- package/dist/chrome/prisma-store.js +48 -0
- package/dist/chrome/prisma-store.js.map +1 -0
- package/dist/chrome/public.d.ts +22 -0
- package/dist/chrome/public.d.ts.map +1 -0
- package/dist/chrome/public.js +181 -0
- package/dist/chrome/public.js.map +1 -0
- package/dist/chrome/resolve.d.ts +36 -0
- package/dist/chrome/resolve.d.ts.map +1 -0
- package/dist/chrome/resolve.js +165 -0
- package/dist/chrome/resolve.js.map +1 -0
- package/dist/chrome/revalidate.d.ts +9 -0
- package/dist/chrome/revalidate.d.ts.map +1 -0
- package/dist/chrome/revalidate.js +51 -0
- package/dist/chrome/revalidate.js.map +1 -0
- package/dist/chrome/routes.d.ts +14 -0
- package/dist/chrome/routes.d.ts.map +1 -0
- package/dist/chrome/routes.js +349 -0
- package/dist/chrome/routes.js.map +1 -0
- package/dist/chrome/schema.d.ts +231 -0
- package/dist/chrome/schema.d.ts.map +1 -0
- package/dist/chrome/schema.js +73 -0
- package/dist/chrome/schema.js.map +1 -0
- package/dist/chrome/service.d.ts +76 -0
- package/dist/chrome/service.d.ts.map +1 -0
- package/dist/chrome/service.js +286 -0
- package/dist/chrome/service.js.map +1 -0
- package/dist/chrome/settings.d.ts +14 -0
- package/dist/chrome/settings.d.ts.map +1 -0
- package/dist/chrome/settings.js +142 -0
- package/dist/chrome/settings.js.map +1 -0
- package/dist/chrome/store.d.ts +48 -0
- package/dist/chrome/store.d.ts.map +1 -0
- package/dist/chrome/store.js +26 -0
- package/dist/chrome/store.js.map +1 -0
- package/dist/chrome/values.d.ts +98 -0
- package/dist/chrome/values.d.ts.map +1 -0
- package/dist/chrome/values.js +491 -0
- package/dist/chrome/values.js.map +1 -0
- package/dist/config/define.d.ts +2 -0
- package/dist/config/define.d.ts.map +1 -1
- package/dist/config/define.js +2 -0
- package/dist/config/define.js.map +1 -1
- package/dist/db/model-types.d.ts +62 -0
- package/dist/db/model-types.d.ts.map +1 -1
- package/dist/forms/attribution-settings.d.ts +23 -0
- package/dist/forms/attribution-settings.d.ts.map +1 -0
- package/dist/forms/attribution-settings.js +42 -0
- package/dist/forms/attribution-settings.js.map +1 -0
- package/dist/forms/attribution.d.ts +5 -1
- package/dist/forms/attribution.d.ts.map +1 -1
- package/dist/forms/attribution.js +6 -4
- package/dist/forms/attribution.js.map +1 -1
- package/dist/forms/client-validation.d.ts.map +1 -1
- package/dist/forms/client-validation.js +3 -0
- package/dist/forms/client-validation.js.map +1 -1
- package/dist/forms/form-model.d.ts +109 -0
- package/dist/forms/form-model.d.ts.map +1 -0
- package/dist/forms/form-model.js +192 -0
- package/dist/forms/form-model.js.map +1 -0
- package/dist/forms/index.d.ts +2 -2
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +1 -1
- package/dist/forms/service.d.ts +19 -1
- package/dist/forms/service.d.ts.map +1 -1
- package/dist/forms/service.js +152 -0
- package/dist/forms/service.js.map +1 -1
- package/dist/forms/spam.d.ts +0 -12
- package/dist/forms/spam.d.ts.map +1 -1
- package/dist/forms/spam.js +30 -0
- package/dist/forms/spam.js.map +1 -1
- package/dist/forms/submission.d.ts +7 -0
- package/dist/forms/submission.d.ts.map +1 -1
- package/dist/forms/submission.js +33 -0
- package/dist/forms/submission.js.map +1 -1
- package/dist/forms/types.d.ts +26 -1
- package/dist/forms/types.d.ts.map +1 -1
- package/dist/forms/types.js +9 -1
- package/dist/forms/types.js.map +1 -1
- package/dist/generated/browser.d.ts +35 -3
- package/dist/generated/client.d.ts +35 -3
- package/dist/generated/internal/class.d.ts +44 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.d.ts +403 -1
- package/dist/generated/internal/prismaNamespace.js +95 -1
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +102 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +95 -1
- package/dist/generated/models/ChromeRegion.d.ts +1203 -0
- package/dist/generated/models/ChromeRegion.js +1 -0
- package/dist/generated/models/Job.d.ts +732 -5
- package/dist/generated/models/JobEvent.d.ts +1339 -0
- package/dist/generated/models/JobEvent.js +1 -0
- package/dist/generated/models/JobReviewItem.d.ts +1470 -0
- package/dist/generated/models/JobReviewItem.js +1 -0
- package/dist/generated/models/SystemSetting.d.ts +952 -0
- package/dist/generated/models/SystemSetting.js +1 -0
- package/dist/generated/models.d.ts +4 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/jobs/batch-ai.d.ts +53 -0
- package/dist/jobs/batch-ai.d.ts.map +1 -0
- package/dist/jobs/batch-ai.js +314 -0
- package/dist/jobs/batch-ai.js.map +1 -0
- package/dist/jobs/contract.d.ts +193 -0
- package/dist/jobs/contract.d.ts.map +1 -0
- package/dist/jobs/contract.js +2 -0
- package/dist/jobs/contract.js.map +1 -0
- package/dist/jobs/error-text.d.ts +23 -0
- package/dist/jobs/error-text.d.ts.map +1 -0
- package/dist/jobs/error-text.js +110 -0
- package/dist/jobs/error-text.js.map +1 -0
- package/dist/jobs/handlers.d.ts.map +1 -1
- package/dist/jobs/handlers.js +48 -7
- package/dist/jobs/handlers.js.map +1 -1
- package/dist/jobs/memory-store.d.ts +26 -2
- package/dist/jobs/memory-store.d.ts.map +1 -1
- package/dist/jobs/memory-store.js +157 -3
- package/dist/jobs/memory-store.js.map +1 -1
- package/dist/jobs/outcomes.d.ts +46 -0
- package/dist/jobs/outcomes.d.ts.map +1 -0
- package/dist/jobs/outcomes.js +251 -0
- package/dist/jobs/outcomes.js.map +1 -0
- package/dist/jobs/prisma-store.d.ts +14 -2
- package/dist/jobs/prisma-store.d.ts.map +1 -1
- package/dist/jobs/prisma-store.js +257 -15
- package/dist/jobs/prisma-store.js.map +1 -1
- package/dist/jobs/queue.d.ts +62 -2
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/jobs/queue.js +412 -36
- package/dist/jobs/queue.js.map +1 -1
- package/dist/jobs/retention.d.ts +11 -0
- package/dist/jobs/retention.d.ts.map +1 -0
- package/dist/jobs/retention.js +18 -0
- package/dist/jobs/retention.js.map +1 -0
- package/dist/jobs/review.d.ts +111 -0
- package/dist/jobs/review.d.ts.map +1 -0
- package/dist/jobs/review.js +461 -0
- package/dist/jobs/review.js.map +1 -0
- package/dist/jobs/routes.d.ts.map +1 -1
- package/dist/jobs/routes.js +491 -16
- package/dist/jobs/routes.js.map +1 -1
- package/dist/jobs/serialize.d.ts +40 -0
- package/dist/jobs/serialize.d.ts.map +1 -0
- package/dist/jobs/serialize.js +275 -0
- package/dist/jobs/serialize.js.map +1 -0
- package/dist/jobs/types.d.ts +141 -2
- package/dist/jobs/types.d.ts.map +1 -1
- package/dist/jobs/worker-status.d.ts +27 -0
- package/dist/jobs/worker-status.d.ts.map +1 -0
- package/dist/jobs/worker-status.js +66 -0
- package/dist/jobs/worker-status.js.map +1 -0
- package/dist/media/optimize.d.ts.map +1 -1
- package/dist/media/optimize.js +4 -2
- package/dist/media/optimize.js.map +1 -1
- package/dist/next-site.d.ts +1 -0
- package/dist/next-site.d.ts.map +1 -1
- package/dist/next-site.js +3 -0
- package/dist/next-site.js.map +1 -1
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js +23 -2
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/seo/issue-fix-link-insert.d.ts +11 -0
- package/dist/seo/issue-fix-link-insert.d.ts.map +1 -1
- package/dist/seo/issue-fix-link-insert.js +122 -8
- package/dist/seo/issue-fix-link-insert.js.map +1 -1
- package/dist/server-site.d.ts.map +1 -1
- package/dist/server-site.js +15 -0
- package/dist/server-site.js.map +1 -1
- package/dist/site.d.ts +8 -0
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +6 -0
- package/dist/site.js.map +1 -1
- package/dist/webhooks/index.d.ts +3 -1
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +6 -1
- package/prisma/migrations/0025_job_cancel_review/down.sql +60 -0
- package/prisma/migrations/0025_job_cancel_review/migration.sql +56 -0
- package/prisma/migrations/0026_job_creator_system_settings/down.sql +30 -0
- package/prisma/migrations/0026_job_creator_system_settings/migration.sql +14 -0
- package/prisma/migrations/0027_chrome_regions/down.sql +24 -0
- package/prisma/migrations/0027_chrome_regions/migration.sql +16 -0
- package/prisma/schema.prisma +105 -15
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { initialsFor } from '../auth/team.js';
|
|
2
|
+
import { errorSummary } from './error-text.js';
|
|
3
|
+
const NO_REFS = { users: new Map(), review: new Map() };
|
|
4
|
+
const SCANS = {
|
|
5
|
+
'seo-scan': {
|
|
6
|
+
title: 'SEO scan',
|
|
7
|
+
scope: 'All indexable pages',
|
|
8
|
+
schedule: 'weekly-seo',
|
|
9
|
+
},
|
|
10
|
+
'content-health': {
|
|
11
|
+
title: 'Content health',
|
|
12
|
+
scope: 'All published documents',
|
|
13
|
+
schedule: 'nightly-health',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
/** Operation names as `/ai/batch` stores them, plus the kit's names unchanged. */
|
|
17
|
+
const BATCH_OPERATIONS = {
|
|
18
|
+
meta: 'meta',
|
|
19
|
+
'alt-text': 'alt',
|
|
20
|
+
summarize: 'excerpt',
|
|
21
|
+
alt: 'alt',
|
|
22
|
+
titles: 'titles',
|
|
23
|
+
excerpt: 'excerpt',
|
|
24
|
+
links: 'links',
|
|
25
|
+
custom: 'custom',
|
|
26
|
+
};
|
|
27
|
+
const OPERATION_TITLES = {
|
|
28
|
+
meta: 'Rewrite meta descriptions',
|
|
29
|
+
alt: 'Generate alt text',
|
|
30
|
+
titles: 'Shorten long titles',
|
|
31
|
+
excerpt: 'Write excerpts',
|
|
32
|
+
links: 'Add internal links',
|
|
33
|
+
custom: 'Custom instruction',
|
|
34
|
+
};
|
|
35
|
+
function isScan(type) {
|
|
36
|
+
return type in SCANS;
|
|
37
|
+
}
|
|
38
|
+
function str(value) {
|
|
39
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
40
|
+
}
|
|
41
|
+
/** The rows `/ai/batch` stores, one per document. */
|
|
42
|
+
function batchItems(job) {
|
|
43
|
+
const items = job.payload.items;
|
|
44
|
+
if (!Array.isArray(items))
|
|
45
|
+
return [];
|
|
46
|
+
return items.filter((item) => !!item && typeof item === 'object');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The kit's payload. Rows queued by `/ai/batch` keep their operation and
|
|
50
|
+
* collection per item and apply directly, so they read as "Apply automatically".
|
|
51
|
+
* They store no review mode, which `origin` keeps visible: unlike a stored
|
|
52
|
+
* `auto`, they aren't admin-only to retry ({@link batchAiWriter}).
|
|
53
|
+
*/
|
|
54
|
+
function batchPayload(job) {
|
|
55
|
+
const payload = job.payload;
|
|
56
|
+
const items = batchItems(job);
|
|
57
|
+
const operation = str(payload.operation) ?? str(items[0]?.operation) ?? '';
|
|
58
|
+
const documentIds = Array.isArray(payload.documentIds)
|
|
59
|
+
? payload.documentIds.filter((id) => typeof id === 'string')
|
|
60
|
+
: items.map((item) => str(item.documentId)).filter((id) => id !== null);
|
|
61
|
+
return {
|
|
62
|
+
operation: BATCH_OPERATIONS[operation] ?? 'custom',
|
|
63
|
+
instruction: str(payload.instruction),
|
|
64
|
+
source: str(payload.source) ?? str(items[0]?.collection) ?? '',
|
|
65
|
+
statusFilter: payload.statusFilter === 'published' ? 'published' : 'all',
|
|
66
|
+
documentIds,
|
|
67
|
+
review: payload.review === 'review' ? 'review' : 'auto',
|
|
68
|
+
onApprove: payload.onApprove === 'publish' ? 'publish' : 'draft',
|
|
69
|
+
...(isTextField(payload.field) ? { field: payload.field } : {}),
|
|
70
|
+
...(payload.review !== 'review' && payload.review !== 'auto'
|
|
71
|
+
? { origin: 'ai-batch' }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function isTextField(value) {
|
|
76
|
+
return value === 'metaDescription' || value === 'seoTitle' || value === 'excerpt';
|
|
77
|
+
}
|
|
78
|
+
function scanPayload(job) {
|
|
79
|
+
return { trigger: job.payload.trigger === 'manual' || job.createdById ? 'manual' : 'cron' };
|
|
80
|
+
}
|
|
81
|
+
/** Who queued it. Older batch AI rows have no `createdById`, so their first item says. */
|
|
82
|
+
function creatorId(job) {
|
|
83
|
+
if (job.createdById)
|
|
84
|
+
return job.createdById;
|
|
85
|
+
if (job.type === 'batch-ai')
|
|
86
|
+
return str(batchItems(job)[0]?.userId);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Who a batch AI job writes as, and where, for permission checks. Each item
|
|
91
|
+
* stores the user and role it was queued with, and the worker writes with them.
|
|
92
|
+
* `autoApply` is true only for a stored `review: 'auto'`. Rows from `/ai/batch`
|
|
93
|
+
* have no review mode: any editor could queue them.
|
|
94
|
+
*/
|
|
95
|
+
export function batchAiWriter(job) {
|
|
96
|
+
const items = batchItems(job);
|
|
97
|
+
const roles = items.map((item) => str(item.role)).filter((role) => role !== null);
|
|
98
|
+
const collections = [str(job.payload.source), ...items.map((item) => str(item.collection))];
|
|
99
|
+
return {
|
|
100
|
+
creatorId: creatorId(job),
|
|
101
|
+
roles: [...new Set(roles)],
|
|
102
|
+
collections: [...new Set(collections.filter((slug) => slug !== null))],
|
|
103
|
+
autoApply: job.payload.review === 'auto',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function userRef(id, users) {
|
|
107
|
+
return users.get(id) ?? { id, name: 'Unknown user', initials: '?' };
|
|
108
|
+
}
|
|
109
|
+
function createdBy(job, refs) {
|
|
110
|
+
const id = creatorId(job);
|
|
111
|
+
if (id)
|
|
112
|
+
return userRef(id, refs.users);
|
|
113
|
+
if (isScan(job.type))
|
|
114
|
+
return { kind: 'cron', schedule: SCANS[job.type].schedule };
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function title(job) {
|
|
118
|
+
if (job.title)
|
|
119
|
+
return job.title;
|
|
120
|
+
if (isScan(job.type))
|
|
121
|
+
return SCANS[job.type].title;
|
|
122
|
+
if (job.type === 'batch-ai')
|
|
123
|
+
return OPERATION_TITLES[batchPayload(job).operation];
|
|
124
|
+
return job.type;
|
|
125
|
+
}
|
|
126
|
+
function scope(job) {
|
|
127
|
+
if (job.scope)
|
|
128
|
+
return job.scope;
|
|
129
|
+
if (isScan(job.type))
|
|
130
|
+
return SCANS[job.type].scope;
|
|
131
|
+
if (job.type === 'batch-ai') {
|
|
132
|
+
const count = batchPayload(job).documentIds.length;
|
|
133
|
+
return `${count} ${count === 1 ? 'document' : 'documents'}`;
|
|
134
|
+
}
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
/** seo-scan saves `skip`, content-health `cursor`, batch AI `index`. */
|
|
138
|
+
function cursor(payload) {
|
|
139
|
+
if (typeof payload.cursor === 'string')
|
|
140
|
+
return payload.cursor;
|
|
141
|
+
if (typeof payload.skip === 'number' && payload.skip > 0)
|
|
142
|
+
return String(payload.skip);
|
|
143
|
+
if (typeof payload.index === 'number' && payload.index > 0)
|
|
144
|
+
return String(payload.index);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
function payloadView(job) {
|
|
148
|
+
if (job.type === 'batch-ai')
|
|
149
|
+
return batchPayload(job);
|
|
150
|
+
if (isScan(job.type))
|
|
151
|
+
return scanPayload(job);
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The stored row in the contract shape (DATA_CONTRACTS §1). `lastError` is one
|
|
156
|
+
* summary line unless `fullError` is set, because the full text has the stack,
|
|
157
|
+
* with server paths and query text. `detail` adds scan results.
|
|
158
|
+
*/
|
|
159
|
+
export function serializeJob(job, refs = NO_REFS, options = {}) {
|
|
160
|
+
const { completed, total, message } = job.progress;
|
|
161
|
+
const serialized = {
|
|
162
|
+
id: job.id,
|
|
163
|
+
type: job.type,
|
|
164
|
+
status: job.status,
|
|
165
|
+
title: title(job),
|
|
166
|
+
scope: scope(job),
|
|
167
|
+
runAt: job.runAt.toISOString(),
|
|
168
|
+
attempts: job.attempts,
|
|
169
|
+
maxAttempts: job.maxAttempts,
|
|
170
|
+
progress: {
|
|
171
|
+
done: completed,
|
|
172
|
+
total,
|
|
173
|
+
unit: job.type === 'seo-scan' ? 'pages' : 'documents',
|
|
174
|
+
cursor: cursor(job.payload),
|
|
175
|
+
completed,
|
|
176
|
+
...(message !== undefined ? { message } : {}),
|
|
177
|
+
},
|
|
178
|
+
lastError: options.fullError ? job.lastError : errorSummary(job.lastError),
|
|
179
|
+
// '' marks a note claimed but not (or never) written.
|
|
180
|
+
explain: job.explain || null,
|
|
181
|
+
summary: job.summary || null,
|
|
182
|
+
lockedAt: job.lockedAt?.toISOString() ?? null,
|
|
183
|
+
cancelRequestedAt: job.cancelRequestedAt?.toISOString() ?? null,
|
|
184
|
+
cancelledBy: job.cancelledById ? userRef(job.cancelledById, refs.users) : null,
|
|
185
|
+
createdBy: createdBy(job, refs),
|
|
186
|
+
createdAt: job.createdAt.toISOString(),
|
|
187
|
+
updatedAt: job.updatedAt.toISOString(),
|
|
188
|
+
finishedAt: job.finishedAt?.toISOString() ?? null,
|
|
189
|
+
usage: { tokensIn: job.tokensIn ?? 0, tokensOut: job.tokensOut ?? 0 },
|
|
190
|
+
payload: payloadView(job),
|
|
191
|
+
};
|
|
192
|
+
if (options.detail && isScan(job.type) && job.results) {
|
|
193
|
+
serialized.results = job.results;
|
|
194
|
+
}
|
|
195
|
+
if (job.type === 'batch-ai') {
|
|
196
|
+
serialized.review = refs.review.get(job.id) ?? { pending: 0, approved: 0, rejected: 0 };
|
|
197
|
+
}
|
|
198
|
+
return serialized;
|
|
199
|
+
}
|
|
200
|
+
export function serializeJobEvent(event) {
|
|
201
|
+
return {
|
|
202
|
+
at: event.at.toISOString(),
|
|
203
|
+
kind: event.kind,
|
|
204
|
+
message: event.message,
|
|
205
|
+
...(event.detail ? { detail: event.detail } : {}),
|
|
206
|
+
...(typeof event.durationMs === 'number' ? { durationMs: event.durationMs } : {}),
|
|
207
|
+
...(typeof event.tokens === 'number' ? { tokens: event.tokens } : {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** People by id, one query. An id with no user is left out; {@link userRef} fills it in. */
|
|
211
|
+
export async function loadUserRefs(db, ids) {
|
|
212
|
+
const unique = [...new Set(ids)];
|
|
213
|
+
const refs = new Map();
|
|
214
|
+
if (unique.length === 0)
|
|
215
|
+
return refs;
|
|
216
|
+
const users = await db.user.findMany({
|
|
217
|
+
where: { id: { in: unique } },
|
|
218
|
+
select: { id: true, name: true, email: true },
|
|
219
|
+
});
|
|
220
|
+
for (const user of users) {
|
|
221
|
+
const name = user.name || user.email;
|
|
222
|
+
refs.set(user.id, { id: user.id, name, initials: initialsFor(user.name, user.email) });
|
|
223
|
+
}
|
|
224
|
+
return refs;
|
|
225
|
+
}
|
|
226
|
+
/** One query for the people on these jobs and one for batch AI review counts. */
|
|
227
|
+
export async function loadJobRefs(db, jobs) {
|
|
228
|
+
const userIds = new Set();
|
|
229
|
+
for (const job of jobs) {
|
|
230
|
+
const creator = creatorId(job);
|
|
231
|
+
if (creator)
|
|
232
|
+
userIds.add(creator);
|
|
233
|
+
if (job.cancelledById)
|
|
234
|
+
userIds.add(job.cancelledById);
|
|
235
|
+
}
|
|
236
|
+
const batchIds = jobs.filter((job) => job.type === 'batch-ai').map((job) => job.id);
|
|
237
|
+
const [users, counts] = await Promise.all([
|
|
238
|
+
loadUserRefs(db, userIds),
|
|
239
|
+
batchIds.length > 0
|
|
240
|
+
? db.jobReviewItem.groupBy({
|
|
241
|
+
by: ['jobId', 'status'],
|
|
242
|
+
where: { jobId: { in: batchIds } },
|
|
243
|
+
_count: { _all: true },
|
|
244
|
+
})
|
|
245
|
+
: [],
|
|
246
|
+
]);
|
|
247
|
+
const review = new Map();
|
|
248
|
+
for (const row of counts) {
|
|
249
|
+
const tally = review.get(row.jobId) ?? { pending: 0, approved: 0, rejected: 0 };
|
|
250
|
+
if (row.status === 'pending' || row.status === 'approved' || row.status === 'rejected') {
|
|
251
|
+
tally[row.status] += row._count._all;
|
|
252
|
+
}
|
|
253
|
+
review.set(row.jobId, tally);
|
|
254
|
+
}
|
|
255
|
+
return { users, review };
|
|
256
|
+
}
|
|
257
|
+
/** A review item in the contract shape, with who decided it. */
|
|
258
|
+
export function serializeReviewItem(item, users) {
|
|
259
|
+
return {
|
|
260
|
+
id: item.id,
|
|
261
|
+
jobId: item.jobId,
|
|
262
|
+
documentId: item.documentId,
|
|
263
|
+
documentTitle: item.documentTitle,
|
|
264
|
+
url: item.url,
|
|
265
|
+
field: item.field,
|
|
266
|
+
before: item.before,
|
|
267
|
+
after: item.after,
|
|
268
|
+
editedAfter: item.editedAfter,
|
|
269
|
+
status: item.status,
|
|
270
|
+
decidedBy: item.decidedById ? userRef(item.decidedById, users) : null,
|
|
271
|
+
decidedAt: item.decidedAt?.toISOString() ?? null,
|
|
272
|
+
appliedRevisionId: item.appliedRevisionId,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=serialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/jobs/serialize.ts"],"names":[],"mappings":"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"}
|
package/dist/jobs/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ScanResults } from './contract.js';
|
|
2
|
+
export type JobStatus = 'queued' | 'running' | 'succeeded' | 'dead' | 'cancelled';
|
|
2
3
|
export interface JobProgress {
|
|
3
4
|
completed: number;
|
|
4
5
|
total: number;
|
|
@@ -17,6 +18,30 @@ export interface JobRecord {
|
|
|
17
18
|
progress: JobProgress;
|
|
18
19
|
createdAt: Date;
|
|
19
20
|
updatedAt: Date;
|
|
21
|
+
/** Set while a running job should stop after the current slice. */
|
|
22
|
+
cancelRequestedAt?: Date | null;
|
|
23
|
+
cancelledById?: string | null;
|
|
24
|
+
/** Who queued the job. Null for cron jobs. Written once, on insert. */
|
|
25
|
+
createdById?: string | null;
|
|
26
|
+
title?: string | null;
|
|
27
|
+
scope?: string | null;
|
|
28
|
+
tokensIn?: number;
|
|
29
|
+
tokensOut?: number;
|
|
30
|
+
/**
|
|
31
|
+
* The AI notes (worker rules 3 and 4): `explain` on a dead job, `summary` on
|
|
32
|
+
* a finished scan. Null until the worker claims the note, then `''` until
|
|
33
|
+
* its text lands. `''` stays when the AI had nothing to say.
|
|
34
|
+
*/
|
|
35
|
+
explain?: string | null;
|
|
36
|
+
summary?: string | null;
|
|
37
|
+
/** Scan results in the contract's `ScanResults` shape, written when a scan finishes. */
|
|
38
|
+
results?: ScanResults | null;
|
|
39
|
+
finishedAt?: Date | null;
|
|
40
|
+
}
|
|
41
|
+
/** AI tokens one slice used. The queue adds them to the job's running totals. */
|
|
42
|
+
export interface JobUsage {
|
|
43
|
+
tokensIn: number;
|
|
44
|
+
tokensOut: number;
|
|
20
45
|
}
|
|
21
46
|
interface JobHandlerContext {
|
|
22
47
|
now: Date;
|
|
@@ -25,12 +50,47 @@ interface JobHandlerContext {
|
|
|
25
50
|
type JobHandlerResult = {
|
|
26
51
|
done: true;
|
|
27
52
|
progress?: JobProgress;
|
|
53
|
+
usage?: JobUsage;
|
|
54
|
+
/** Worker rule 3: a scan's results, saved with the success. The AI summary follows later. */
|
|
55
|
+
results?: ScanResults;
|
|
28
56
|
} | {
|
|
29
57
|
done: false;
|
|
30
58
|
progress: JobProgress;
|
|
31
59
|
payload?: Record<string, unknown>;
|
|
60
|
+
usage?: JobUsage;
|
|
61
|
+
/**
|
|
62
|
+
* Yield until `until` instead of running again at once, for example when
|
|
63
|
+
* an AI usage cap is spent. Attempts stay put: waiting is not a failure.
|
|
64
|
+
*/
|
|
65
|
+
wait?: {
|
|
66
|
+
until: Date;
|
|
67
|
+
reason: string;
|
|
68
|
+
};
|
|
32
69
|
};
|
|
33
70
|
export type JobHandler = (job: JobRecord, ctx: JobHandlerContext) => Promise<JobHandlerResult>;
|
|
71
|
+
export type JobEventKind = 'queued' | 'claimed' | 'slice_ok' | 'slice_failed' | 'yielded' | 'retry_scheduled' | 'dead' | 'cancel_requested' | 'cancelled' | 'finished' | 'stale_lock';
|
|
72
|
+
export interface JobEventRecord {
|
|
73
|
+
jobId: string;
|
|
74
|
+
at: Date;
|
|
75
|
+
kind: JobEventKind;
|
|
76
|
+
message: string;
|
|
77
|
+
detail?: string | null;
|
|
78
|
+
durationMs?: number | null;
|
|
79
|
+
tokens?: number | null;
|
|
80
|
+
}
|
|
81
|
+
/** Which AI note: `explain` on a dead job, `summary` on a finished scan. */
|
|
82
|
+
export type JobNote = 'explain' | 'summary';
|
|
83
|
+
export interface JobListFilter {
|
|
84
|
+
type?: string;
|
|
85
|
+
statuses?: JobStatus[];
|
|
86
|
+
}
|
|
87
|
+
/** How much one retention run may delete. */
|
|
88
|
+
export interface PruneLimit {
|
|
89
|
+
/** Jobs, at most. */
|
|
90
|
+
jobs: number;
|
|
91
|
+
/** Events and review items deleted with them, at most, past the first job. */
|
|
92
|
+
rows: number;
|
|
93
|
+
}
|
|
34
94
|
export interface JobStore {
|
|
35
95
|
insert(job: JobRecord): Promise<void>;
|
|
36
96
|
/** A queued or running job of this type, if one exists. */
|
|
@@ -39,14 +99,93 @@ export interface JobStore {
|
|
|
39
99
|
claimNext(now: Date): Promise<JobRecord | null>;
|
|
40
100
|
/** Running jobs locked before `cutoff` (the worker was killed mid-slice). */
|
|
41
101
|
findStaleRunning(cutoff: Date): Promise<JobRecord[]>;
|
|
102
|
+
/**
|
|
103
|
+
* Writes the row except the cancel fields and `createdById`, so a slice saving
|
|
104
|
+
* a copy it read earlier can never clear a cancel. Only
|
|
105
|
+
* {@link JobStore.requestCancel} sets the cancel fields and only
|
|
106
|
+
* {@link JobStore.retry} clears them.
|
|
107
|
+
*/
|
|
42
108
|
save(job: JobRecord): Promise<void>;
|
|
43
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Sets the cancel fields and `updatedAt` only, as one conditional write. It does
|
|
111
|
+
* nothing when a cancel was already requested or the job is no longer queued or
|
|
112
|
+
* running, so a cancel that loses a race with the job finishing leaves no trace.
|
|
113
|
+
* Returns whether this call set the flag.
|
|
114
|
+
*/
|
|
115
|
+
requestCancel(id: string, at: Date, byId: string | null): Promise<boolean>;
|
|
116
|
+
/**
|
|
117
|
+
* Writes `job` (already marked cancelled) like {@link JobStore.save}, but only
|
|
118
|
+
* while the stored row is still queued or running and still waits on the same
|
|
119
|
+
* cancel: its `cancelRequestedAt` equals `job.cancelRequestedAt`. So a stale
|
|
120
|
+
* copy can't undo a retry that cleared the flag, end a job twice, or finish a
|
|
121
|
+
* newer cancel requested after a retry with fields from before it. Returns
|
|
122
|
+
* whether it applied.
|
|
123
|
+
*/
|
|
124
|
+
finishCancel(job: JobRecord): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Puts a dead job, or a queued job waiting out a failure, back in the queue
|
|
127
|
+
* now, as one conditional write. Resets attempts and clears `lastError`,
|
|
128
|
+
* `explain`, `finishedAt` and the cancel fields. Payload and progress are kept,
|
|
129
|
+
* so the handler continues from its saved cursor. Returns whether it applied.
|
|
130
|
+
*/
|
|
131
|
+
retry(id: string, now: Date): Promise<boolean>;
|
|
132
|
+
/**
|
|
133
|
+
* Finished jobs whose AI note nobody has claimed: dead jobs with `explain`
|
|
134
|
+
* null, and succeeded jobs of `summaryTypes` with `summary` null, that
|
|
135
|
+
* finished at or after `since`. Oldest first, at most `limit`.
|
|
136
|
+
*/
|
|
137
|
+
findPendingNotes(since: Date, summaryTypes: readonly string[], limit: number): Promise<JobRecord[]>;
|
|
138
|
+
/**
|
|
139
|
+
* Claims a finished job's AI note as one conditional write: sets it from null
|
|
140
|
+
* to `''` while the job is still the same finish (dead for `explain`,
|
|
141
|
+
* succeeded for `summary`, same `finishedAt`). Only the caller that gets
|
|
142
|
+
* true asks the AI, so each finish gets at most one call, even from two
|
|
143
|
+
* overlapping ticks. A retry clears `explain` back to null.
|
|
144
|
+
*/
|
|
145
|
+
claimNote(id: string, note: JobNote, finishedAt: Date): Promise<boolean>;
|
|
146
|
+
/**
|
|
147
|
+
* Adds the note's tokens to the job, then writes its text while the note is
|
|
148
|
+
* still claimed (`''`) for the same finish, so an answer can't land on a job
|
|
149
|
+
* that was retried since. The tokens are added either way. Returns whether
|
|
150
|
+
* the text was written.
|
|
151
|
+
*/
|
|
152
|
+
writeNote(id: string, note: JobNote, finishedAt: Date, text: string, usage?: JobUsage): Promise<boolean>;
|
|
153
|
+
/** Newest first. */
|
|
154
|
+
list(limit: number, filter?: JobListFilter): Promise<JobRecord[]>;
|
|
155
|
+
get(id: string): Promise<JobRecord | null>;
|
|
156
|
+
/**
|
|
157
|
+
* Queued jobs whose cancel flag was set. The worker finalises these at the
|
|
158
|
+
* start of each tick: a cancel can land just before a slice yields, and a job
|
|
159
|
+
* waiting out a usage cap may not be claimed again for weeks. Running jobs are
|
|
160
|
+
* left to the tick that holds their lock, which finalises the cancel at its
|
|
161
|
+
* slice boundary with that slice's work, or to the stale-lock path once the
|
|
162
|
+
* lock is older than 10 minutes.
|
|
163
|
+
*/
|
|
164
|
+
findCancelRequested(): Promise<JobRecord[]>;
|
|
165
|
+
/**
|
|
166
|
+
* Retention: deletes finished jobs (succeeded, dead or cancelled) that
|
|
167
|
+
* finished before `cutoff`, oldest first, with their events and review items,
|
|
168
|
+
* within `limit`. A row with no `finishedAt` (finished before 0025) counts
|
|
169
|
+
* from `updatedAt`. Queued and running jobs are never deleted, and neither is
|
|
170
|
+
* a job with a change waiting for review or decided since `cutoff`, so a
|
|
171
|
+
* decision can be undone for the whole window. Returns how many it deleted.
|
|
172
|
+
*/
|
|
173
|
+
pruneFinished(cutoff: Date, limit: PruneLimit): Promise<number>;
|
|
174
|
+
addEvent(event: JobEventRecord): Promise<void>;
|
|
175
|
+
/** Newest first. */
|
|
176
|
+
listEvents(jobId: string, limit: number): Promise<JobEventRecord[]>;
|
|
177
|
+
/**
|
|
178
|
+
* Deletes all but a job's newest `keep` events, in {@link JobStore.listEvents}
|
|
179
|
+
* order. Returns how many it deleted.
|
|
180
|
+
*/
|
|
181
|
+
trimEvents(jobId: string, keep: number): Promise<number>;
|
|
44
182
|
}
|
|
45
183
|
export interface QueueTickResult {
|
|
46
184
|
succeeded: number;
|
|
47
185
|
continued: number;
|
|
48
186
|
retried: number;
|
|
49
187
|
dead: number;
|
|
188
|
+
cancelled: number;
|
|
50
189
|
}
|
|
51
190
|
export {};
|
|
52
191
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/jobs/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jobs/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PrentaDB } from '../db/client-types.js';
|
|
2
|
+
import type { JobWorkerSchedule, JobWorkerStatus } from './contract.js';
|
|
3
|
+
import type { JobStore } from './types.js';
|
|
4
|
+
/** Time one worker tick may spend running slices. */
|
|
5
|
+
export declare const WORKER_BUDGET_MS = 20000;
|
|
6
|
+
interface ScanSchedule {
|
|
7
|
+
type: JobWorkerSchedule['type'];
|
|
8
|
+
cron: string;
|
|
9
|
+
/** UTC day of the week, 0 = Sunday. Null runs every day. */
|
|
10
|
+
weekday: number | null;
|
|
11
|
+
hour: number;
|
|
12
|
+
minute: number;
|
|
13
|
+
}
|
|
14
|
+
/** The first time after `now` that a daily or weekly UTC schedule fires. */
|
|
15
|
+
export declare function nextScheduledRun(schedule: Pick<ScanSchedule, 'weekday' | 'hour' | 'minute'>, now: Date): Date;
|
|
16
|
+
/**
|
|
17
|
+
* Stamp a finished worker tick: when it started and how long it ran. A settings
|
|
18
|
+
* row, so no job or user is needed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function recordWorkerTick(db: PrentaDB, tick: {
|
|
21
|
+
at: Date;
|
|
22
|
+
ms: number;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
/** `GET /jobs/worker`: the worker strip on the Jobs screen. */
|
|
25
|
+
export declare function getWorkerStatus(db: PrentaDB, store: JobStore, now: Date): Promise<JobWorkerStatus>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=worker-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-status.d.ts","sourceRoot":"","sources":["../../src/jobs/worker-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAM1C,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC,UAAU,YAAY;IACpB,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,4DAA4D;IAC5D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAQD,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC,EAC3D,GAAG,EAAE,IAAI,GACR,IAAI,CAeN;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAOf;AAWD,+DAA+D;AAC/D,wBAAsB,eAAe,CACnC,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,eAAe,CAAC,CAsB1B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** `prenta_system_settings` key for the worker's last tick. */
|
|
2
|
+
const WORKER_TICK_KEY = 'jobs.worker';
|
|
3
|
+
/** The worker cron (`/cron/jobs`) runs once a minute. */
|
|
4
|
+
const TICK_INTERVAL_MS = 60_000;
|
|
5
|
+
/** Time one worker tick may spend running slices. */
|
|
6
|
+
export const WORKER_BUDGET_MS = 20_000;
|
|
7
|
+
/** The scan crons as create-prenta schedules them, in UTC. */
|
|
8
|
+
const SCAN_SCHEDULES = [
|
|
9
|
+
{ type: 'seo-scan', cron: '0 4 * * 1', weekday: 1, hour: 4, minute: 0 },
|
|
10
|
+
{ type: 'content-health', cron: '30 2 * * *', weekday: null, hour: 2, minute: 30 },
|
|
11
|
+
];
|
|
12
|
+
/** The first time after `now` that a daily or weekly UTC schedule fires. */
|
|
13
|
+
export function nextScheduledRun(schedule, now) {
|
|
14
|
+
const next = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), schedule.hour, schedule.minute));
|
|
15
|
+
if (next.getTime() <= now.getTime())
|
|
16
|
+
next.setUTCDate(next.getUTCDate() + 1);
|
|
17
|
+
while (schedule.weekday !== null && next.getUTCDay() !== schedule.weekday) {
|
|
18
|
+
next.setUTCDate(next.getUTCDate() + 1);
|
|
19
|
+
}
|
|
20
|
+
return next;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Stamp a finished worker tick: when it started and how long it ran. A settings
|
|
24
|
+
* row, so no job or user is needed.
|
|
25
|
+
*/
|
|
26
|
+
export async function recordWorkerTick(db, tick) {
|
|
27
|
+
const value = { lastTickAt: tick.at.toISOString(), lastTickMs: tick.ms };
|
|
28
|
+
await db.systemSetting.upsert({
|
|
29
|
+
where: { key: WORKER_TICK_KEY },
|
|
30
|
+
create: { key: WORKER_TICK_KEY, value },
|
|
31
|
+
update: { value },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function readWorkerTick(db) {
|
|
35
|
+
const row = await db.systemSetting.findUnique({ where: { key: WORKER_TICK_KEY } });
|
|
36
|
+
const value = row?.value;
|
|
37
|
+
if (typeof value?.lastTickAt !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
const at = new Date(value.lastTickAt);
|
|
40
|
+
if (Number.isNaN(at.getTime()))
|
|
41
|
+
return null;
|
|
42
|
+
return { at, ms: typeof value.lastTickMs === 'number' ? value.lastTickMs : null };
|
|
43
|
+
}
|
|
44
|
+
/** `GET /jobs/worker`: the worker strip on the Jobs screen. */
|
|
45
|
+
export async function getWorkerStatus(db, store, now) {
|
|
46
|
+
const [tick, schedules] = await Promise.all([
|
|
47
|
+
readWorkerTick(db),
|
|
48
|
+
Promise.all(SCAN_SCHEDULES.map(async (schedule) => {
|
|
49
|
+
const active = await store.findActive(schedule.type);
|
|
50
|
+
return {
|
|
51
|
+
type: schedule.type,
|
|
52
|
+
cron: schedule.cron,
|
|
53
|
+
nextAt: nextScheduledRun(schedule, now).toISOString(),
|
|
54
|
+
activeJobId: active?.id ?? null,
|
|
55
|
+
};
|
|
56
|
+
})),
|
|
57
|
+
]);
|
|
58
|
+
return {
|
|
59
|
+
lastTickAt: tick?.at.toISOString() ?? null,
|
|
60
|
+
lastTickMs: tick?.ms ?? null,
|
|
61
|
+
nextTickAt: tick ? new Date(tick.at.getTime() + TICK_INTERVAL_MS).toISOString() : null,
|
|
62
|
+
budgetMs: WORKER_BUDGET_MS,
|
|
63
|
+
schedules,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=worker-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-status.js","sourceRoot":"","sources":["../../src/jobs/worker-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/media/optimize.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,GAAG,UAAU,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,oFAAoF;AACpF,eAAO,MAAM,mCAAmC,QAAQ,CAAA;AACxD,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,KAAK,CAAA;AACtC,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,KAAK,CAAA;AA+D5C;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GACvC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW7B;AAcD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,EACxC,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../../src/media/optimize.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,GAAG,UAAU,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,oFAAoF;AACpF,eAAO,MAAM,mCAAmC,QAAQ,CAAA;AACxD,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,KAAK,CAAA;AACtC,uEAAuE;AACvE,eAAO,MAAM,0BAA0B,KAAK,CAAA;AA+D5C;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GACvC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW7B;AAcD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,EACxC,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,kBAAkB,CAAC,CA2F7B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU;;;;;;;UAmB9E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
|
package/dist/media/optimize.js
CHANGED
|
@@ -134,10 +134,12 @@ export async function optimizeImage(input, originalFilename, mimeType, opts = {}
|
|
|
134
134
|
})
|
|
135
135
|
.toBuffer();
|
|
136
136
|
const optimizedSize = optimizedBuffer.length;
|
|
137
|
-
// If WebP is not smaller, keep the original bytes — no pointless
|
|
137
|
+
// If lossy WebP is not smaller, keep the original bytes — no pointless
|
|
138
|
+
// re-encode. A requested lossless WebP is kept even when it grows: a JPEG
|
|
139
|
+
// screenshot would otherwise stay a JPEG, and the caller asked for WebP.
|
|
138
140
|
// Still attach dimensions + blur so a later /optimize or backfill can heal
|
|
139
141
|
// rows that were stored without metadata when sharp was missing.
|
|
140
|
-
if (optimizedSize >= originalSize) {
|
|
142
|
+
if (!useLossless && optimizedSize >= originalSize) {
|
|
141
143
|
return {
|
|
142
144
|
...unchangedResult(inputBuffer, originalSize, mimeType, originalFilename, metadata.width ?? 0, metadata.height ?? 0),
|
|
143
145
|
blurHash: await tryBlurHash(sharp, inputBuffer),
|