@nuxtseo/protocol 0.1.5 → 0.2.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/dist/v1/gsc.js CHANGED
@@ -574,6 +574,194 @@ export const urlInspectionReadOperation = defineOperation({
574
574
  examples: [{ request: { params: { siteId: 'site_01JXYZ' }, query: { url: '/docs' } }, response: { status: 200, body: { data: { _tag: 'no-inspection-data', requestedUrl: 'https://example.com/docs' }, meta: { requestId: 'req_01JXYZ', version: '1.0' } } } }],
575
575
  },
576
576
  });
577
+ export const indexCohortsQuerySchema = schema.strictObject({
578
+ limit: schema.coerce.number().int().min(1).max(50).default(12),
579
+ minSectionPages: schema.coerce.number().int().min(1).max(500).default(5),
580
+ });
581
+ export const establishedCohortSchema = defineResponseObject({
582
+ family: schema.string(),
583
+ dimension: schema.string(),
584
+ /**
585
+ * Null when the cohort is not expressible as one path prefix. Every
586
+ * `lifecycle` cohort is: version-pinned pages span the whole Site, so a
587
+ * drill-through must fall back to the unfiltered not-indexed list.
588
+ */
589
+ pathPrefix: schema.string().nullable(),
590
+ pages: schema.number().int().nonnegative(),
591
+ notIndexed: schema.number().int().nonnegative(),
592
+ notIndexedPercent: schema.number(),
593
+ restOfSitePercent: schema.number(),
594
+ restOfSitePages: schema.number().int().nonnegative(),
595
+ confidenceIntervalLower: schema.number(),
596
+ confidenceIntervalUpper: schema.number(),
597
+ /** Families nest, so subtract this before summing notIndexed down the list. */
598
+ alreadyCountedAbove: schema.number().int().nonnegative(),
599
+ }, {
600
+ family: schema.string(),
601
+ dimension: schema.string(),
602
+ pathPrefix: schema.string().nullable(),
603
+ pages: schema.number().int().nonnegative(),
604
+ notIndexed: schema.number().int().nonnegative(),
605
+ notIndexedPercent: schema.number(),
606
+ restOfSitePercent: schema.number(),
607
+ restOfSitePages: schema.number().int().nonnegative(),
608
+ confidenceIntervalLower: schema.number(),
609
+ confidenceIntervalUpper: schema.number(),
610
+ alreadyCountedAbove: schema.number().int().nonnegative(),
611
+ });
612
+ export const rankedCohortSchema = defineResponseObject({
613
+ family: schema.string(),
614
+ pages: schema.number().int().nonnegative(),
615
+ notIndexed: schema.number().int().nonnegative(),
616
+ notIndexedPercent: schema.number(),
617
+ /** False means this family has NOT been shown to differ from the rest of the Site. */
618
+ statisticallyEstablished: schema.boolean(),
619
+ belowMinimumSample: schema.boolean(),
620
+ }, {
621
+ family: schema.string(),
622
+ pages: schema.number().int().nonnegative(),
623
+ notIndexed: schema.number().int().nonnegative(),
624
+ notIndexedPercent: schema.number(),
625
+ statisticallyEstablished: schema.boolean(),
626
+ belowMinimumSample: schema.boolean(),
627
+ });
628
+ const cohortBaselineShape = {
629
+ pages: schema.number().int().nonnegative(),
630
+ notIndexed: schema.number().int().nonnegative(),
631
+ notIndexedPercent: schema.number(),
632
+ };
633
+ const cohortCoverageShape = {
634
+ analysed: schema.number().int().nonnegative(),
635
+ notIndexedButNeverCrawled: schema.number().int().nonnegative(),
636
+ excludedFragments: schema.number().int().nonnegative(),
637
+ };
638
+ /**
639
+ * Two lists that mean different things and must never be merged.
640
+ * `established` holds only families whose Wilson score interval clears the
641
+ * not-indexed rate of their own complement after a Bonferroni correction; those
642
+ * are the only rows that may be reported as a finding. `ranked` orders every
643
+ * family by raw rate with no test applied, so it always answers "where is
644
+ * indexing worst" — a high rate there is a lead to verify, never a conclusion.
645
+ */
646
+ export const indexCohortsDataSchema = defineResponseObject({
647
+ analysed: schema.boolean(),
648
+ reason: schema.string().nullable(),
649
+ message: schema.string().nullable(),
650
+ asOf: schema.string().nullable(),
651
+ baseline: schema.strictObject(cohortBaselineShape).nullable(),
652
+ coverage: schema.strictObject(cohortCoverageShape).nullable(),
653
+ method: schema.string().nullable(),
654
+ familiesTested: schema.number().int().nonnegative(),
655
+ established: schema.array(establishedCohortSchema.producer),
656
+ establishedNote: schema.string().nullable(),
657
+ ranked: schema.array(rankedCohortSchema.producer),
658
+ rankedTotal: schema.number().int().nonnegative(),
659
+ minSectionPages: schema.number().int().positive(),
660
+ }, {
661
+ analysed: schema.boolean(),
662
+ reason: schema.string().nullable(),
663
+ message: schema.string().nullable(),
664
+ asOf: schema.string().nullable(),
665
+ baseline: schema.looseObject(cohortBaselineShape).nullable(),
666
+ coverage: schema.looseObject(cohortCoverageShape).nullable(),
667
+ method: schema.string().nullable(),
668
+ familiesTested: schema.number().int().nonnegative(),
669
+ established: schema.array(establishedCohortSchema.client),
670
+ establishedNote: schema.string().nullable(),
671
+ ranked: schema.array(rankedCohortSchema.client),
672
+ rankedTotal: schema.number().int().nonnegative(),
673
+ minSectionPages: schema.number().int().positive(),
674
+ });
675
+ export const indexCohortsResponseEnvelopeSchema = defineSuccessEnvelope(indexCohortsDataSchema);
676
+ export const indexCohortsReadOperation = defineOperation({
677
+ id: 'sites.search.indexing.cohorts.read',
678
+ method: 'GET',
679
+ path: '/api/v1/sites/{siteId}/search/indexing/cohorts',
680
+ visibility: 'public',
681
+ semantics: { kind: 'query', sideEffects: 'none', retry: 'idempotent', readConsistency: 'primary' },
682
+ auth: {
683
+ credentials: ['session', 'api_token'],
684
+ scopes: ['sites:read'],
685
+ minimumRole: 'viewer',
686
+ ownership: 'site',
687
+ },
688
+ request: { params: searchConsoleSiteParamsSchema, query: indexCohortsQuerySchema, headers: null, body: null },
689
+ responses: { 200: indexCohortsResponseEnvelopeSchema },
690
+ // The same list every other Search read declares: this operation enforces the
691
+ // `search.collect_interactive` monitoring fence, whose denials present as
692
+ // `entitlement_required` and `conflict`.
693
+ // A pure D1 read: it cannot fail on a provider or on stale evidence, so it
694
+ // does not inherit this feature's shared read errors.
695
+ errors: [
696
+ 'invalid_request',
697
+ 'unauthorized',
698
+ 'auth_expired',
699
+ 'forbidden',
700
+ 'not_found',
701
+ 'entitlement_required',
702
+ 'conflict',
703
+ 'rate_limited',
704
+ 'contract_violation',
705
+ 'internal_error',
706
+ ],
707
+ errorResponse: publicV1ErrorEnvelope,
708
+ resources: { reads: [{ type: 'site.collection', idFrom: 'params.siteId' }], changes: [] },
709
+ policy: {
710
+ rate: publicV1StandardRatePolicy,
711
+ idempotency: { _tag: 'none' },
712
+ entitlement: null,
713
+ meter: null,
714
+ },
715
+ lifecycle: { introduced: '1.2.0' },
716
+ docs: {
717
+ summary: 'Read indexing by route family',
718
+ description: 'Returns the not-indexed rate per route family, joined from the Site crawl to Google per-URL index state, so an indexing failure that clusters in one template surfaces as one finding instead of many page-level ones. Read it before listing individual not-indexed URLs. `established` holds only families proven worse than the rest of the Site after a Bonferroni correction; `ranked` orders every family by raw rate with no test applied and is leads only. `analysed: false` means there is no completed crawl or no index state to join, which is not the same as healthy indexing.',
719
+ tags: ['Search Console'],
720
+ examples: [{
721
+ request: { params: { siteId: 'site_01JXYZ' }, query: { limit: 12, minSectionPages: 5 } },
722
+ response: {
723
+ status: 200,
724
+ body: {
725
+ data: {
726
+ analysed: true,
727
+ reason: null,
728
+ message: null,
729
+ asOf: '2026-09-01T00:00:00.000Z',
730
+ baseline: { pages: 1200, notIndexed: 180, notIndexedPercent: 15 },
731
+ coverage: { analysed: 1200, notIndexedButNeverCrawled: 40, excludedFragments: 2 },
732
+ method: 'Wilson score interval per family against its own complement, Bonferroni-corrected across 9 tested families (critical value z=2.77).',
733
+ familiesTested: 9,
734
+ established: [{
735
+ family: '/docs',
736
+ dimension: 'depth-1',
737
+ pathPrefix: '/docs',
738
+ pages: 420,
739
+ notIndexed: 150,
740
+ notIndexedPercent: 35.7,
741
+ restOfSitePercent: 3.8,
742
+ restOfSitePages: 780,
743
+ confidenceIntervalLower: 31.2,
744
+ confidenceIntervalUpper: 40.4,
745
+ alreadyCountedAbove: 0,
746
+ }],
747
+ establishedNote: null,
748
+ ranked: [{
749
+ family: '/docs',
750
+ pages: 420,
751
+ notIndexed: 150,
752
+ notIndexedPercent: 35.7,
753
+ statisticallyEstablished: true,
754
+ belowMinimumSample: false,
755
+ }],
756
+ rankedTotal: 9,
757
+ minSectionPages: 5,
758
+ },
759
+ meta: { requestId: 'req_01JXYZ', version: '1.0' },
760
+ },
761
+ },
762
+ }],
763
+ },
764
+ });
577
765
  export const searchV1Registry = defineRegistry({
578
766
  feature: 'sites',
579
767
  operations: {
@@ -582,5 +770,6 @@ export const searchV1Registry = defineRegistry({
582
770
  readIndexingDiagnostics: indexingDiagnosticsReadOperation,
583
771
  readIndexingHistory: indexingHistoryReadOperation,
584
772
  readUrlInspection: urlInspectionReadOperation,
773
+ readIndexingCohorts: indexCohortsReadOperation,
585
774
  },
586
775
  });