@nuxtseo/protocol 0.5.8 → 0.5.10
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/dist/v1/account.d.ts +2 -2
- package/dist/v1/account.js +1 -1
- package/dist/v1/actions.d.ts +31 -68
- package/dist/v1/actions.js +10 -21
- package/dist/v1/backlinks.d.ts +7 -7
- package/dist/v1/backlinks.js +2 -2
- package/dist/v1/performance.d.ts +2046 -306
- package/dist/v1/performance.js +298 -47
- package/generated/openapi.v1.json +1 -1
- package/package.json +2 -2
package/dist/v1/performance.js
CHANGED
|
@@ -355,26 +355,76 @@ export const fieldVitalFindingsQuerySchema = schema.strictObject({
|
|
|
355
355
|
limit: schema.coerce.number().int().min(1).max(50).default(20),
|
|
356
356
|
offset: schema.coerce.number().int().min(0).default(0),
|
|
357
357
|
});
|
|
358
|
-
|
|
359
|
-
metric: schema.enum(['lcp', 'inp', 'cls']),
|
|
358
|
+
const fieldVitalFindingBaseShape = {
|
|
360
359
|
severity: fieldVitalRatingSchema,
|
|
361
360
|
p75: schema.number().nullable(),
|
|
362
361
|
route: schema.string(),
|
|
363
362
|
selector: schema.string(),
|
|
364
363
|
estimatedViews: schema.number(),
|
|
364
|
+
/** Share of sampled views rated poor, 0 to 1. Null when the provider sent no distribution. */
|
|
365
|
+
poorFraction: schema.number().min(0).max(1).nullable(),
|
|
366
|
+
/** The blocking script a lab Scan of the same route found. Null when no Scan matches. */
|
|
367
|
+
labHint: schema.string().nullable(),
|
|
365
368
|
fixPrompt: schema.string().min(1),
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
};
|
|
370
|
+
// A newer client reading an older server sees no structured fields, so the
|
|
371
|
+
// client marks each one optional.
|
|
372
|
+
const fieldVitalFindingBaseClientShape = {
|
|
373
|
+
...fieldVitalFindingBaseShape,
|
|
374
|
+
poorFraction: fieldVitalFindingBaseShape.poorFraction.optional(),
|
|
375
|
+
labHint: fieldVitalFindingBaseShape.labHint.optional(),
|
|
376
|
+
};
|
|
377
|
+
/** Mean LCP sub-phase durations in milliseconds across the sampled views. */
|
|
378
|
+
const fieldVitalLcpPhasesShape = {
|
|
379
|
+
timeToFirstByte: schema.number().nullable(),
|
|
380
|
+
resourceLoadDelay: schema.number().nullable(),
|
|
381
|
+
resourceLoadTime: schema.number().nullable(),
|
|
382
|
+
elementRenderDelay: schema.number().nullable(),
|
|
383
|
+
};
|
|
384
|
+
const fieldVitalLcpShape = {
|
|
385
|
+
/** Host and path of the LCP resource. Null for a text LCP element. */
|
|
386
|
+
resource: schema.string().nullable(),
|
|
387
|
+
/** The `fetchpriority` the browser saw on the LCP resource. */
|
|
388
|
+
fetchPriority: schema.string().nullable(),
|
|
389
|
+
/** How the resource was requested, for example `img` or `css`. */
|
|
390
|
+
initiatorType: schema.string().nullable(),
|
|
391
|
+
phases: schema.strictObject(fieldVitalLcpPhasesShape),
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* One failing real-user vital attributed to a DOM element. The fields after
|
|
395
|
+
* `selector` depend on `metric`: an LCP finding names its resource and phases,
|
|
396
|
+
* an INP finding names the interaction. `fixPrompt` restates the same facts as
|
|
397
|
+
* prose for an agent to apply.
|
|
398
|
+
*/
|
|
399
|
+
export const fieldVitalFindingSchema = {
|
|
400
|
+
producer: schema.discriminatedUnion('metric', [
|
|
401
|
+
schema.strictObject({ metric: schema.literal('lcp'), ...fieldVitalFindingBaseShape, ...fieldVitalLcpShape }),
|
|
402
|
+
schema.strictObject({ metric: schema.literal('inp'), ...fieldVitalFindingBaseShape, interaction: schema.string().nullable() }),
|
|
403
|
+
schema.strictObject({ metric: schema.literal('cls'), ...fieldVitalFindingBaseShape }),
|
|
404
|
+
]),
|
|
405
|
+
client: schema.discriminatedUnion('metric', [
|
|
406
|
+
schema.looseObject({
|
|
407
|
+
metric: schema.literal('lcp'),
|
|
408
|
+
...fieldVitalFindingBaseClientShape,
|
|
409
|
+
resource: fieldVitalLcpShape.resource.optional(),
|
|
410
|
+
fetchPriority: fieldVitalLcpShape.fetchPriority.optional(),
|
|
411
|
+
initiatorType: fieldVitalLcpShape.initiatorType.optional(),
|
|
412
|
+
phases: schema.looseObject(fieldVitalLcpPhasesShape).optional(),
|
|
413
|
+
}),
|
|
414
|
+
schema.looseObject({ metric: schema.literal('inp'), ...fieldVitalFindingBaseClientShape, interaction: schema.string().nullable().optional() }),
|
|
415
|
+
schema.looseObject({ metric: schema.literal('cls'), ...fieldVitalFindingBaseClientShape }),
|
|
416
|
+
]),
|
|
417
|
+
};
|
|
418
|
+
/** The dates the Web Analytics provider aggregated, inclusive, as `YYYY-MM-DD`. */
|
|
419
|
+
const fieldVitalWindowShape = {
|
|
420
|
+
from: schema.iso.date(),
|
|
421
|
+
to: schema.iso.date(),
|
|
422
|
+
};
|
|
375
423
|
export const fieldVitalFindingsDataSchema = defineResponseObject({
|
|
376
424
|
source: schema.string().nullable(),
|
|
377
425
|
message: schema.string().nullable(),
|
|
426
|
+
/** Null when no Web Analytics provider answered. */
|
|
427
|
+
window: schema.strictObject(fieldVitalWindowShape).nullable(),
|
|
378
428
|
findings: schema.array(fieldVitalFindingSchema.producer),
|
|
379
429
|
total: schema.number().int().nonnegative(),
|
|
380
430
|
limit: schema.number().int().positive(),
|
|
@@ -382,6 +432,7 @@ export const fieldVitalFindingsDataSchema = defineResponseObject({
|
|
|
382
432
|
}, {
|
|
383
433
|
source: schema.string().nullable(),
|
|
384
434
|
message: schema.string().nullable(),
|
|
435
|
+
window: schema.looseObject(fieldVitalWindowShape).nullable().optional(),
|
|
385
436
|
findings: schema.array(fieldVitalFindingSchema.client),
|
|
386
437
|
total: schema.number().int().nonnegative(),
|
|
387
438
|
limit: schema.number().int().positive(),
|
|
@@ -427,7 +478,7 @@ export const fieldVitalFindingsOperation = defineOperation({
|
|
|
427
478
|
lifecycle: { introduced: '1.2.0' },
|
|
428
479
|
docs: {
|
|
429
480
|
summary: 'List field Core Web Vitals findings',
|
|
430
|
-
description: 'Returns each failing real-user vital attributed to the DOM element behind it, with the p75, the CSS selector, and a ready-to-apply fix prompt. It requires a connected Web Analytics provider. If no provider is connected, findings is empty and message says so.',
|
|
481
|
+
description: 'Returns each failing real-user vital attributed to the DOM element behind it, with the p75, the CSS selector, and a ready-to-apply fix prompt. An LCP finding also names its resource, fetchPriority and phases. An INP finding names the interaction. window gives the dates the provider aggregated. It requires a connected Web Analytics provider. If no provider is connected, findings is empty and message says so.',
|
|
431
482
|
tags: ['Pages'],
|
|
432
483
|
examples: [{
|
|
433
484
|
request: { params: { siteId: 'site_01JXYZ' }, query: { onlyFailing: '1', limit: 20, offset: 0 } },
|
|
@@ -437,6 +488,7 @@ export const fieldVitalFindingsOperation = defineOperation({
|
|
|
437
488
|
data: {
|
|
438
489
|
source: 'cloudflare',
|
|
439
490
|
message: null,
|
|
491
|
+
window: { from: '2026-07-13', to: '2026-08-09' },
|
|
440
492
|
findings: [{
|
|
441
493
|
metric: 'lcp',
|
|
442
494
|
severity: 'poor',
|
|
@@ -444,6 +496,12 @@ export const fieldVitalFindingsOperation = defineOperation({
|
|
|
444
496
|
route: '/blog',
|
|
445
497
|
selector: 'main > img.hero',
|
|
446
498
|
estimatedViews: 1840,
|
|
499
|
+
poorFraction: 0.31,
|
|
500
|
+
labHint: null,
|
|
501
|
+
resource: 'example.com/images/hero.jpg',
|
|
502
|
+
fetchPriority: 'auto',
|
|
503
|
+
initiatorType: 'img',
|
|
504
|
+
phases: { timeToFirstByte: 600, resourceLoadDelay: 1900, resourceLoadTime: 1200, elementRenderDelay: 600 },
|
|
447
505
|
fixPrompt: 'The LCP element on /blog is main > img.hero at 4300ms.',
|
|
448
506
|
}],
|
|
449
507
|
total: 1,
|
|
@@ -475,31 +533,130 @@ const scanScoresShape = {
|
|
|
475
533
|
bestPractices: nullableMetricSchema,
|
|
476
534
|
seo: nullableMetricSchema,
|
|
477
535
|
};
|
|
478
|
-
|
|
536
|
+
/**
|
|
537
|
+
* Why a Scan started. `claim-recheck` follows a claimed Action. `deploy`
|
|
538
|
+
* follows a new Nuxt build. `confirmation` repeats a Scan that looked like a
|
|
539
|
+
* regression.
|
|
540
|
+
*/
|
|
541
|
+
export const PERFORMANCE_SCAN_TRIGGERS = ['scheduled', 'manual', 'claim-recheck', 'deploy', 'confirmation'];
|
|
542
|
+
/**
|
|
543
|
+
* Why a Scan failed. `timeout`, `unavailable`, `quota`, `rejected`, `network`,
|
|
544
|
+
* `empty`, `malformed` and `oversize` describe the PageSpeed Insights request.
|
|
545
|
+
* The rest describe the pipeline around it. `rejected` means PSI refused the
|
|
546
|
+
* URL, so a retry of the same URL fails the same way.
|
|
547
|
+
*/
|
|
548
|
+
export const PERFORMANCE_SCAN_FAILURE_KINDS = [
|
|
549
|
+
'timeout',
|
|
550
|
+
'unavailable',
|
|
551
|
+
'quota',
|
|
552
|
+
'rejected',
|
|
553
|
+
'network',
|
|
554
|
+
'empty',
|
|
555
|
+
'malformed',
|
|
556
|
+
'oversize',
|
|
557
|
+
'artifact-missing',
|
|
558
|
+
'processing',
|
|
559
|
+
'abandoned',
|
|
560
|
+
'unqueued',
|
|
561
|
+
'monitoring-changed',
|
|
562
|
+
'internal',
|
|
563
|
+
];
|
|
564
|
+
/**
|
|
565
|
+
* Fields every Scan carries, in the Scan list and the Scan read alike.
|
|
566
|
+
* `failureKind` and `error` are null unless `status` is `failed`. A Scan that
|
|
567
|
+
* failed before failure kinds were recorded has a null `failureKind`.
|
|
568
|
+
*/
|
|
569
|
+
const scanBaseShape = {
|
|
479
570
|
id: schema.string().min(1),
|
|
480
571
|
url: schema.string().min(1),
|
|
481
|
-
strategy: schema.
|
|
482
|
-
status: schema.
|
|
483
|
-
trigger: schema.
|
|
572
|
+
strategy: schema.enum(['mobile', 'desktop']),
|
|
573
|
+
status: schema.enum(['running', 'complete', 'failed']),
|
|
574
|
+
trigger: schema.enum(PERFORMANCE_SCAN_TRIGGERS),
|
|
575
|
+
failureKind: schema.enum(PERFORMANCE_SCAN_FAILURE_KINDS).nullable(),
|
|
576
|
+
error: schema.string().nullable(),
|
|
577
|
+
lighthouseVersion: schema.string().nullable(),
|
|
484
578
|
scores: schema.strictObject(scanScoresShape),
|
|
485
579
|
lcp: nullableMetricSchema,
|
|
486
580
|
tbt: nullableMetricSchema,
|
|
487
581
|
cls: nullableMetricSchema,
|
|
488
582
|
completedAt: schema.string().datetime().nullable(),
|
|
489
|
-
|
|
490
|
-
|
|
583
|
+
};
|
|
584
|
+
// Strings, not enums, so an older client accepts a status, trigger or failure
|
|
585
|
+
// kind that a newer server adds. Fields added after 1.2.0 are optional.
|
|
586
|
+
const scanBaseClientShape = {
|
|
491
587
|
id: schema.string().min(1),
|
|
492
588
|
url: schema.string().min(1),
|
|
493
589
|
strategy: schema.string().min(1),
|
|
494
590
|
status: schema.string().min(1),
|
|
495
|
-
trigger: schema.string().nullable(),
|
|
591
|
+
trigger: schema.string().nullable().optional(),
|
|
592
|
+
failureKind: schema.string().nullable().optional(),
|
|
593
|
+
error: schema.string().nullable().optional(),
|
|
594
|
+
lighthouseVersion: schema.string().nullable().optional(),
|
|
496
595
|
scores: schema.looseObject(scanScoresShape),
|
|
497
596
|
lcp: nullableMetricSchema,
|
|
498
597
|
tbt: nullableMetricSchema,
|
|
499
598
|
cls: nullableMetricSchema,
|
|
500
599
|
completedAt: schema.string().datetime().nullable(),
|
|
501
|
-
|
|
502
|
-
|
|
600
|
+
};
|
|
601
|
+
export const performanceScanSummarySchema = defineResponseObject(scanBaseShape, scanBaseClientShape);
|
|
602
|
+
/** Metrics a Scan comparison can name. Same names and units as the Scan fields. */
|
|
603
|
+
export const PERFORMANCE_SCAN_COMPARISON_METRICS = ['perf', 'a11y', 'bestPractices', 'seo', 'lcp', 'tbt', 'cls', 'inp', 'fcp', 'speedIndex', 'ttfb'];
|
|
604
|
+
/** Why two Scans of one Page cannot be compared. */
|
|
605
|
+
export const PERFORMANCE_SCAN_INCOMPATIBILITY_REASONS = [
|
|
606
|
+
'source',
|
|
607
|
+
'target-url',
|
|
608
|
+
'strategy',
|
|
609
|
+
'lighthouse-version',
|
|
610
|
+
'form-factor',
|
|
611
|
+
'cpu-throttling',
|
|
612
|
+
'network-throttling',
|
|
613
|
+
'host-browser',
|
|
614
|
+
'runtime-environment',
|
|
615
|
+
];
|
|
616
|
+
const scanChangeSeveritySchema = schema.enum(['improvement', 'regression', 'neutral']);
|
|
617
|
+
const scanMetricChangeShape = {
|
|
618
|
+
metric: schema.enum(PERFORMANCE_SCAN_COMPARISON_METRICS),
|
|
619
|
+
previous: schema.number(),
|
|
620
|
+
current: schema.number(),
|
|
621
|
+
delta: schema.number(),
|
|
622
|
+
severity: scanChangeSeveritySchema,
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* This Scan against the previous comparable Scan of the same Page and strategy.
|
|
626
|
+
* `compared` lists each metric both Scans measured. `incompatible` names why
|
|
627
|
+
* the two Scans differ, for example a Lighthouse version change. `none` means
|
|
628
|
+
* no previous Scan was compared, which is always true for the homepage slot.
|
|
629
|
+
*/
|
|
630
|
+
export const performanceScanComparisonSchema = {
|
|
631
|
+
producer: schema.discriminatedUnion('_tag', [
|
|
632
|
+
schema.strictObject({
|
|
633
|
+
_tag: schema.literal('compared'),
|
|
634
|
+
previousScanId: schema.string().min(1).nullable(),
|
|
635
|
+
severity: scanChangeSeveritySchema,
|
|
636
|
+
changes: schema.array(schema.strictObject(scanMetricChangeShape)),
|
|
637
|
+
}),
|
|
638
|
+
schema.strictObject({
|
|
639
|
+
_tag: schema.literal('incompatible'),
|
|
640
|
+
previousScanId: schema.string().min(1).nullable(),
|
|
641
|
+
reasons: schema.array(schema.enum(PERFORMANCE_SCAN_INCOMPATIBILITY_REASONS)),
|
|
642
|
+
}),
|
|
643
|
+
schema.strictObject({ _tag: schema.literal('none') }),
|
|
644
|
+
]),
|
|
645
|
+
client: schema.discriminatedUnion('_tag', [
|
|
646
|
+
schema.looseObject({
|
|
647
|
+
_tag: schema.literal('compared'),
|
|
648
|
+
previousScanId: schema.string().min(1).nullable(),
|
|
649
|
+
severity: schema.string().min(1),
|
|
650
|
+
changes: schema.array(schema.looseObject({ ...scanMetricChangeShape, metric: schema.string().min(1), severity: schema.string().min(1) })),
|
|
651
|
+
}),
|
|
652
|
+
schema.looseObject({
|
|
653
|
+
_tag: schema.literal('incompatible'),
|
|
654
|
+
previousScanId: schema.string().min(1).nullable(),
|
|
655
|
+
reasons: schema.array(schema.string().min(1)),
|
|
656
|
+
}),
|
|
657
|
+
schema.looseObject({ _tag: schema.literal('none') }),
|
|
658
|
+
]),
|
|
659
|
+
};
|
|
503
660
|
export const performanceScansDataSchema = defineResponseObject({
|
|
504
661
|
scans: schema.array(performanceScanSummarySchema.producer),
|
|
505
662
|
total: schema.number().int().nonnegative(),
|
|
@@ -546,7 +703,7 @@ export const performanceScansOperation = defineOperation({
|
|
|
546
703
|
lifecycle: { introduced: '1.2.0' },
|
|
547
704
|
docs: {
|
|
548
705
|
summary: 'List Lighthouse Scans',
|
|
549
|
-
description: 'Returns retained Lighthouse Scans for the Site, newest first, with the scores and lab vitals each one recorded. A failed Scan carries its error. Use one Scan id with the Scan read for the failing checks behind a score.',
|
|
706
|
+
description: 'Returns retained Lighthouse Scans for the Site, newest first, with the scores and lab vitals each one recorded. A failed Scan carries its failureKind and error, and its scores are null. Use one Scan id with the Scan read for the failing checks behind a score.',
|
|
550
707
|
tags: ['Pages'],
|
|
551
708
|
examples: [{
|
|
552
709
|
request: { params: { siteId: 'site_01JXYZ' }, query: { limit: 25 } },
|
|
@@ -560,14 +717,30 @@ export const performanceScansOperation = defineOperation({
|
|
|
560
717
|
strategy: 'mobile',
|
|
561
718
|
status: 'complete',
|
|
562
719
|
trigger: 'scheduled',
|
|
720
|
+
failureKind: null,
|
|
721
|
+
error: null,
|
|
722
|
+
lighthouseVersion: '13.5.0',
|
|
563
723
|
scores: { perf: 82, a11y: 96, bestPractices: 92, seo: 98 },
|
|
564
724
|
lcp: 2200,
|
|
565
725
|
tbt: 180,
|
|
566
726
|
cls: 0.08,
|
|
567
727
|
completedAt: '2026-08-10T01:00:00.000Z',
|
|
568
|
-
|
|
728
|
+
}, {
|
|
729
|
+
id: 'scan_01JXZA',
|
|
730
|
+
url: 'https://example.com/blog',
|
|
731
|
+
strategy: 'mobile',
|
|
732
|
+
status: 'failed',
|
|
733
|
+
trigger: 'scheduled',
|
|
734
|
+
failureKind: 'timeout',
|
|
735
|
+
error: 'PSI timed out loading the page',
|
|
736
|
+
lighthouseVersion: null,
|
|
737
|
+
scores: { perf: null, a11y: null, bestPractices: null, seo: null },
|
|
738
|
+
lcp: null,
|
|
739
|
+
tbt: null,
|
|
740
|
+
cls: null,
|
|
741
|
+
completedAt: '2026-08-10T01:05:00.000Z',
|
|
569
742
|
}],
|
|
570
|
-
total:
|
|
743
|
+
total: 2,
|
|
571
744
|
limit: 25,
|
|
572
745
|
},
|
|
573
746
|
meta: { requestId: 'req_01JXYZ', version: '1.0' },
|
|
@@ -576,17 +749,29 @@ export const performanceScansOperation = defineOperation({
|
|
|
576
749
|
}],
|
|
577
750
|
},
|
|
578
751
|
});
|
|
752
|
+
/**
|
|
753
|
+
* Why the scheduler stopped scanning a Page. `gone`: the URL answers 404 or
|
|
754
|
+
* 410. `redirected`: it redirects to another page. `unreachable`: Scans keep
|
|
755
|
+
* timing out or failing. `rejected`: PageSpeed Insights keeps refusing it.
|
|
756
|
+
* Null while the Page is scanned on schedule.
|
|
757
|
+
*/
|
|
758
|
+
const MONITORED_PAGE_PAUSE_REASONS = ['gone', 'redirected', 'unreachable', 'rejected'];
|
|
579
759
|
export const monitoredPageSchema = defineResponseObject({
|
|
580
760
|
id: schema.string().min(1),
|
|
581
761
|
path: schema.string(),
|
|
582
762
|
source: schema.string().nullable(),
|
|
583
763
|
enabled: schema.boolean(),
|
|
764
|
+
pauseReason: schema.enum(MONITORED_PAGE_PAUSE_REASONS).nullable(),
|
|
765
|
+
pausedAt: schema.string().datetime().nullable(),
|
|
584
766
|
lastScanAt: schema.string().datetime().nullable(),
|
|
585
767
|
}, {
|
|
586
768
|
id: schema.string().min(1),
|
|
587
769
|
path: schema.string(),
|
|
588
770
|
source: schema.string().nullable(),
|
|
589
771
|
enabled: schema.boolean(),
|
|
772
|
+
// A client accepts reasons a newer server adds.
|
|
773
|
+
pauseReason: schema.string().min(1).nullable().optional(),
|
|
774
|
+
pausedAt: schema.string().datetime().nullable().optional(),
|
|
590
775
|
lastScanAt: schema.string().datetime().nullable(),
|
|
591
776
|
});
|
|
592
777
|
export const monitoredPagesDataSchema = defineResponseObject({
|
|
@@ -633,7 +818,7 @@ export const monitoredPagesOperation = defineOperation({
|
|
|
633
818
|
lifecycle: { introduced: '1.2.0' },
|
|
634
819
|
docs: {
|
|
635
820
|
summary: 'List monitored Pages',
|
|
636
|
-
description: 'Returns the Pages this Site
|
|
821
|
+
description: 'Returns the Pages this Site monitors, in display order, with the last time each one was scanned. A Page with a pauseReason is out of the schedule until the owner adds it again. Start a Scan for a new Page with the Page scan operation.',
|
|
637
822
|
tags: ['Pages'],
|
|
638
823
|
examples: [{
|
|
639
824
|
request: { params: { siteId: 'site_01JXYZ' } },
|
|
@@ -641,8 +826,11 @@ export const monitoredPagesOperation = defineOperation({
|
|
|
641
826
|
status: 200,
|
|
642
827
|
body: {
|
|
643
828
|
data: {
|
|
644
|
-
pages: [
|
|
645
|
-
|
|
829
|
+
pages: [
|
|
830
|
+
{ id: 'lhp_01JXYZ', path: '/pricing', source: 'auto-gsc', enabled: true, pauseReason: null, pausedAt: null, lastScanAt: '2026-08-10T01:00:00.000Z' },
|
|
831
|
+
{ id: 'lhp_01JXZA', path: '/blog/old-post', source: 'auto-gsc', enabled: true, pauseReason: 'redirected', pausedAt: '2026-08-09T01:00:00.000Z', lastScanAt: '2026-08-09T01:00:00.000Z' },
|
|
832
|
+
],
|
|
833
|
+
total: 2,
|
|
646
834
|
},
|
|
647
835
|
meta: { requestId: 'req_01JXYZ', version: '1.0' },
|
|
648
836
|
},
|
|
@@ -764,21 +952,65 @@ export const lcpObservedTraceSchema = schema.discriminatedUnion('_tag', [
|
|
|
764
952
|
context.addIssue({ code: 'custom', message: 'Trace phases must describe the observed LCP' });
|
|
765
953
|
}
|
|
766
954
|
});
|
|
955
|
+
const scanCruxVitalShape = {
|
|
956
|
+
p75: schema.number().nonnegative(),
|
|
957
|
+
rating: fieldVitalRatingSchema,
|
|
958
|
+
};
|
|
959
|
+
const scanCruxVitalClientShape = {
|
|
960
|
+
p75: schema.number(),
|
|
961
|
+
rating: schema.string().min(1),
|
|
962
|
+
};
|
|
963
|
+
/** CrUX for one scope as PSI returned it with a Scan. `no_data` means CrUX holds no record for that scope. */
|
|
964
|
+
const scanCruxReadingSchema = {
|
|
965
|
+
producer: schema.discriminatedUnion('_tag', [
|
|
966
|
+
schema.strictObject({
|
|
967
|
+
_tag: schema.literal('measured'),
|
|
968
|
+
target: schema.string(),
|
|
969
|
+
lcp: schema.strictObject(scanCruxVitalShape).nullable(),
|
|
970
|
+
inp: schema.strictObject(scanCruxVitalShape).nullable(),
|
|
971
|
+
cls: schema.strictObject(scanCruxVitalShape).nullable(),
|
|
972
|
+
}),
|
|
973
|
+
schema.strictObject({ _tag: schema.literal('no_data'), target: schema.string() }),
|
|
974
|
+
]),
|
|
975
|
+
client: schema.discriminatedUnion('_tag', [
|
|
976
|
+
schema.looseObject({
|
|
977
|
+
_tag: schema.literal('measured'),
|
|
978
|
+
target: schema.string(),
|
|
979
|
+
lcp: schema.looseObject(scanCruxVitalClientShape).nullable(),
|
|
980
|
+
inp: schema.looseObject(scanCruxVitalClientShape).nullable(),
|
|
981
|
+
cls: schema.looseObject(scanCruxVitalClientShape).nullable(),
|
|
982
|
+
}),
|
|
983
|
+
schema.looseObject({ _tag: schema.literal('no_data'), target: schema.string() }),
|
|
984
|
+
]),
|
|
985
|
+
};
|
|
986
|
+
/**
|
|
987
|
+
* The CrUX field reading PSI returned with the Scan: real Chrome users' p75
|
|
988
|
+
* over the 28 days before it, for the Scan URL and its origin. Null on a Scan
|
|
989
|
+
* processed before these readings were stored.
|
|
990
|
+
*/
|
|
991
|
+
const scanCruxSchema = {
|
|
992
|
+
producer: schema.strictObject({
|
|
993
|
+
formFactor: schema.enum(['PHONE', 'DESKTOP']),
|
|
994
|
+
capturedAt: schema.string().datetime(),
|
|
995
|
+
url: scanCruxReadingSchema.producer.nullable(),
|
|
996
|
+
origin: scanCruxReadingSchema.producer.nullable(),
|
|
997
|
+
}).nullable(),
|
|
998
|
+
client: schema.looseObject({
|
|
999
|
+
formFactor: schema.string().min(1),
|
|
1000
|
+
capturedAt: schema.string().datetime(),
|
|
1001
|
+
url: scanCruxReadingSchema.client.nullable(),
|
|
1002
|
+
origin: scanCruxReadingSchema.client.nullable(),
|
|
1003
|
+
}).nullable(),
|
|
1004
|
+
};
|
|
767
1005
|
export const performanceScanDetailDataSchema = defineResponseObject({
|
|
768
|
-
|
|
769
|
-
url: schema.string().min(1),
|
|
770
|
-
strategy: schema.string().min(1),
|
|
771
|
-
status: schema.string().min(1),
|
|
772
|
-
scores: schema.strictObject(scanScoresShape),
|
|
773
|
-
lcp: nullableMetricSchema,
|
|
774
|
-
tbt: nullableMetricSchema,
|
|
775
|
-
cls: nullableMetricSchema,
|
|
1006
|
+
...scanBaseShape,
|
|
776
1007
|
fcp: nullableMetricSchema,
|
|
777
1008
|
speedIndex: nullableMetricSchema,
|
|
778
1009
|
ttfb: nullableMetricSchema,
|
|
779
|
-
|
|
1010
|
+
comparison: performanceScanComparisonSchema.producer,
|
|
780
1011
|
javascript: schema.strictObject(scanJavascriptShape).nullable(),
|
|
781
1012
|
observedTrace: lcpObservedTraceSchema.optional(),
|
|
1013
|
+
crux: scanCruxSchema.producer,
|
|
782
1014
|
performanceIssues: schema.array(scanPerformanceIssueSchema.producer),
|
|
783
1015
|
cachePolicy: schema.array(scanCachePolicySchema.producer),
|
|
784
1016
|
thirdParties: schema.array(scanThirdPartySchema.producer),
|
|
@@ -791,20 +1023,14 @@ export const performanceScanDetailDataSchema = defineResponseObject({
|
|
|
791
1023
|
bestPractices: schema.array(scanFailingCheckSchema.producer),
|
|
792
1024
|
coverage: schema.strictObject(scanCoverageShape),
|
|
793
1025
|
}, {
|
|
794
|
-
|
|
795
|
-
url: schema.string().min(1),
|
|
796
|
-
strategy: schema.string().min(1),
|
|
797
|
-
status: schema.string().min(1),
|
|
798
|
-
scores: schema.looseObject(scanScoresShape),
|
|
799
|
-
lcp: nullableMetricSchema,
|
|
800
|
-
tbt: nullableMetricSchema,
|
|
801
|
-
cls: nullableMetricSchema,
|
|
1026
|
+
...scanBaseClientShape,
|
|
802
1027
|
fcp: nullableMetricSchema,
|
|
803
1028
|
speedIndex: nullableMetricSchema,
|
|
804
1029
|
ttfb: nullableMetricSchema,
|
|
805
|
-
|
|
1030
|
+
comparison: performanceScanComparisonSchema.client.optional(),
|
|
806
1031
|
javascript: schema.looseObject(scanJavascriptShape).nullable().optional(),
|
|
807
1032
|
observedTrace: lcpObservedTraceSchema.optional(),
|
|
1033
|
+
crux: scanCruxSchema.client.optional(),
|
|
808
1034
|
performanceIssues: schema.array(scanPerformanceIssueSchema.client),
|
|
809
1035
|
cachePolicy: schema.array(scanCachePolicySchema.client),
|
|
810
1036
|
thirdParties: schema.array(scanThirdPartySchema.client),
|
|
@@ -854,7 +1080,7 @@ export const performanceScanDetailOperation = defineOperation({
|
|
|
854
1080
|
lifecycle: { introduced: '1.2.0' },
|
|
855
1081
|
docs: {
|
|
856
1082
|
summary: 'Get one Lighthouse Scan',
|
|
857
|
-
description: 'Returns one Scan with the evidence behind its scores: the wasteful assets, the repeat-visit cache policy, the third-party scripts, the LCP element, and every failing accessibility and best-practices check with the CSS selectors and DOM snippets that failed. Read coverage before quoting a total: elements and checks are both capped per Scan.',
|
|
1083
|
+
description: 'Returns one Scan with the evidence behind its scores: the wasteful assets, the repeat-visit cache policy, the third-party scripts, the LCP element, and every failing accessibility and best-practices check with the CSS selectors and DOM snippets that failed. It also returns why the Scan started, the Lighthouse version, and the comparison with the previous Scan of the same Page. crux is the CrUX p75 for the Scan URL and its origin that PSI returned with the Scan, null on a Scan stored before these readings. A failed Scan carries its failureKind and error. Read coverage before quoting a total: elements and checks are both capped per Scan. A null blockingTime or wastedMs is unmeasured, never zero.',
|
|
858
1084
|
tags: ['Pages'],
|
|
859
1085
|
examples: [{
|
|
860
1086
|
request: { params: { siteId: 'site_01JXYZ', scanId: 'scan_01JXYZ' } },
|
|
@@ -863,9 +1089,13 @@ export const performanceScanDetailOperation = defineOperation({
|
|
|
863
1089
|
body: {
|
|
864
1090
|
data: {
|
|
865
1091
|
id: 'scan_01JXYZ',
|
|
866
|
-
url: 'https://example.com/',
|
|
1092
|
+
url: 'https://example.com/pricing',
|
|
867
1093
|
strategy: 'mobile',
|
|
868
1094
|
status: 'complete',
|
|
1095
|
+
trigger: 'scheduled',
|
|
1096
|
+
failureKind: null,
|
|
1097
|
+
error: null,
|
|
1098
|
+
lighthouseVersion: '13.5.0',
|
|
869
1099
|
scores: { perf: 82, a11y: 96, bestPractices: 92, seo: 98 },
|
|
870
1100
|
lcp: 2200,
|
|
871
1101
|
tbt: 180,
|
|
@@ -874,7 +1104,28 @@ export const performanceScanDetailOperation = defineOperation({
|
|
|
874
1104
|
speedIndex: 2400,
|
|
875
1105
|
ttfb: 210,
|
|
876
1106
|
completedAt: '2026-08-10T01:00:00.000Z',
|
|
1107
|
+
comparison: {
|
|
1108
|
+
_tag: 'compared',
|
|
1109
|
+
previousScanId: 'scan_01JXXX',
|
|
1110
|
+
severity: 'regression',
|
|
1111
|
+
changes: [
|
|
1112
|
+
{ metric: 'lcp', previous: 1500, current: 2200, delta: 700, severity: 'regression' },
|
|
1113
|
+
{ metric: 'perf', previous: 85, current: 82, delta: -3, severity: 'neutral' },
|
|
1114
|
+
],
|
|
1115
|
+
},
|
|
877
1116
|
javascript: null,
|
|
1117
|
+
crux: {
|
|
1118
|
+
formFactor: 'PHONE',
|
|
1119
|
+
capturedAt: '2026-08-10T00:58:00.000Z',
|
|
1120
|
+
url: { _tag: 'no_data', target: 'https://example.com/pricing' },
|
|
1121
|
+
origin: {
|
|
1122
|
+
_tag: 'measured',
|
|
1123
|
+
target: 'https://example.com',
|
|
1124
|
+
lcp: { p75: 2100, rating: 'good' },
|
|
1125
|
+
inp: { p75: 180, rating: 'good' },
|
|
1126
|
+
cls: { p75: 0.05, rating: 'good' },
|
|
1127
|
+
},
|
|
1128
|
+
},
|
|
878
1129
|
performanceIssues: [],
|
|
879
1130
|
cachePolicy: [],
|
|
880
1131
|
thirdParties: [],
|