Documentation
¶
Overview ¶
Package discovery builds PayCLI's adaptive capability manifest (§7).
The package exists because one binary has to drive *any* Payload 3.x project: slugs, GraphQL type names, id types, upload/versions/drafts/trash support and the queryable field set are all project-specific, and several of them are not obtainable at all from some deployments. Three rules therefore govern every line of it:
**Shape, never status.** Every REST probe validates the shape of the response body. A custom collection endpoint can shadow any built-in route, an unauthenticated /api/access answers 200 with a truncated view, and a wrong base URL answers 200 text/html.
**Tri-state, never defaulted** (§7.6). Every fact only GraphQL can establish is value | unknown, with a *_source sibling. An unknown value must never cause a local rejection: a defaulted id_type would make PayCLI reject valid Mongo ObjectIds with an error message that is a lie.
**Read-only by default** (§7.7). Nothing here creates a document unless the caller passed AllowWriteProbes, and §8.4's Level-3 reactive invalidation is never armed from this package — discovery deliberately generates every Level-3 trigger, so an ambient rule would recurse.
The package never imports the CLI layer or cobra, never reads a clock of its own (Options.Now is injected), and never writes to disk: it returns a typed Manifest plus field Shards and lets internal/cache persist them.
Index ¶
- Constants
- Variables
- func AttributeOperatorFailure(serverFailed bool, operators []string) (string, bool)
- func BlockSchemasForShard(shard *Shard, schema *Schema, src BlockSources) map[string]BlockTypeSchema
- func CensusDocKey(id any) string
- func ClosestSlug(s string, pool []string) string
- func CompatibleTypes(a, b *CensusType) bool
- func ContainsArray(v any) bool
- func ContainsBlockRows(v any) bool
- func ContainsContent(v any) bool
- func CrossValidateLocales(candidates, enumNames []string) bool
- func DefaultRichTextValue() map[string]any
- func DescribeBlocksHelp(interfaces []string, profile, fieldPath string) string
- func FlagsFromObject(obj *IntroType) (drafts, trash, folders *bool)
- func FormatName(slug string) string
- func HashShard(s *Shard) string
- func HumanizeField(name string) string
- func IDString(v any) string
- func IDTypeFromArg(f *IntroField) (string, bool)
- func IDTypeOfJSON(v any) (string, bool)
- func InferDBAdapter(idTypes []string, sampleIDs []string) (adapter, source string)
- func IsComplexityError(msgs []string) bool
- func IsInternal(slug string) bool
- func IsJoinType(t *IntroType) bool
- func IsRelationshipType(t *IntroType) bool
- func IsUploadType(t *IntroType) bool
- func LinkLikeValue(s string) bool
- func LocaleEnumNames(schema *Schema) []string
- func LocaleUnverifiedHint(profile string) string
- func LocalesValidatable(source string) bool
- func LooksNumeric(s string) bool
- func Mitigation(code string) string
- func NewBlockRow(slug string, stats *CensusType, gql *BlockTypeSchema) (map[string]any, []string)
- func NormalizeBlockPath(p string) string
- func ObservedBlockTypes(docs []map[string]any) map[string][]string
- func OperatorFailureCandidate(operator string) bool
- func OperatorsFor(payloadType, operatorTypeName string) []string
- func ParseDeleteMessage(message string) (string, bool)
- func ResolveIDType(in IDLadderInput) (idType, source string)
- func ResolveLocales(in LocaleInput) (locales []string, source string, count *int)
- func ResolveShardBlocks(shard *Shard, schema *Schema, src BlockSources)
- func RouteMissingHint(graphQLPath, apiPath, graphQLRoute, graphQLPathSource string) string
- func SchemaSHA256(queryFieldNames []string) string
- func SlugFromInterfaceName(name string) string
- func SortableKind(payloadType string) bool
- func TitleField(shard *Shard) *string
- func TitleFromSlug(slug string) string
- func TopologySHA256(p TopologyProjection) string
- func TouchesBlocks(doc map[string]any, k *BlockKnowledge) bool
- func UnionMembers(schema *Schema, graphQLType *string) []string
- func UnresolvedBlocksReason(fieldPath string) string
- func UploadFromObject(obj *IntroType) *bool
- func UseAPIKey(input *IntroType) *bool
- func ValueAtRowPath(doc map[string]any, path string) (any, bool)
- func WriteShapeFor(payloadType string, polymorphic, hasMany bool) *string
- type AcceptedSet
- type AcceptedType
- type AuthBootstrap
- type BlockDoc
- type BlockField
- type BlockFieldSchema
- type BlockFieldView
- type BlockIssue
- type BlockKnowledge
- func (k *BlockKnowledge) Accepted(pattern string) *AcceptedSet
- func (k *BlockKnowledge) GraphQLSchema(slug string) (BlockTypeSchema, bool)
- func (k *BlockKnowledge) HasObservedBlocks() bool
- func (k *BlockKnowledge) KnownTypes() []string
- func (k *BlockKnowledge) ProjectTypes() map[string]bool
- func (k *BlockKnowledge) ShardSlug(slug string) string
- func (k *BlockKnowledge) TopLevelPaths() []string
- func (k *BlockKnowledge) TypeStats(slug string) *CensusType
- type BlockResolution
- type BlockSchemaView
- type BlockSourceDecl
- type BlockSourceField
- type BlockSources
- type BlockTypeSchema
- type BlockUse
- type BlockValidation
- type Capabilities
- type Census
- type CensusBuilder
- type CensusChange
- type CensusEntity
- type CensusField
- type CensusOptions
- type CensusPath
- type CensusRef
- type CensusTarget
- type CensusType
- type CensusValue
- type Collection
- type Diagnostics
- type Discoverer
- type Field
- type FieldDoc
- type Fingerprint
- type Flags
- type FlagsSource
- type Global
- type GlobalFlags
- type GlobalFlagsSource
- type GlobalPermissions
- type GraphQLCapability
- type GraphQLNames
- type IDLadderInput
- type Identity
- type IntroArg
- type IntroEnumValue
- type IntroField
- type IntroInputField
- type IntroType
- type IntroTypeName
- type Jobs
- type Kind
- type Labels
- type LearnedFailure
- type Limitation
- type LocaleAnalysis
- type LocaleInput
- type Localization
- type Manifest
- func (m *Manifest) AddLimitation(code, entity, field, detail string)
- func (m *Manifest) AddUnreachable(slug, kind, reason, detail string)
- func (m *Manifest) Collection(slug string) (*Collection, bool)
- func (m *Manifest) CollectionSlugs() []string
- func (m *Manifest) Degrade(stage string)
- func (m *Manifest) Global(slug string) (*Global, bool)
- func (m *Manifest) GlobalSlugs() []string
- func (m *Manifest) LearnedOperatorFailure(operator, collection string) (LearnedFailure, bool)
- func (m *Manifest) RecordOperatorFailure(operator, collection, evidence string, at time.Time) bool
- func (m *Manifest) ResolveCollection(slug string) (*Collection, error)
- func (m *Manifest) ResolveGlobal(slug string) (*Global, error)
- func (m *Manifest) Revision() string
- func (m *Manifest) Sort()
- type ManifestMeta
- type Options
- type Permissions
- type Preferences
- type Result
- type Schema
- type Shard
- func (s *Shard) BlockFieldFor(path string) (BlockField, bool)
- func (s *Shard) BlockSchemaFor(slug string) (BlockTypeSchema, bool)
- func (s *Shard) BlockSlugsFor() []string
- func (s *Shard) BlockTypesFor(path string) ([]string, bool)
- func (s *Shard) DateFields() []string
- func (s *Shard) DocumentedPaths() []string
- func (s *Shard) Field(path string) (Field, bool)
- func (s *Shard) FieldDocFor(path string) (FieldDoc, bool)
- func (s *Shard) Finalize()
- func (s *Shard) Paths() []string
- func (s *Shard) QueryablePaths() []string
- func (s *Shard) SetBlockField(bf BlockField)
- func (s *Shard) SetFieldDocs(docs map[string]FieldDoc, file, source string)
- func (s *Shard) SortablePaths() []string
- type ShardLookup
- type Source
- type Stats
- type TopologyProjection
- type TypeRef
- type Unreachable
- type UnsupportedOperator
Constants ¶
const ( CensusKindBlocks = "blocks" CensusKindArray = "array" CensusKindRichText = "richText" CensusKindGroup = "group" CensusKindRelationship = "relationship" CensusKindUpload = "upload" CensusKindList = "list" CensusKindScalar = "scalar" CensusKindUnknown = "unknown" )
Census kinds: what a field's values LOOK like across every observed row.
const ( EntityKindCollection = "collection" EntityKindGlobal = "global" )
Entity kinds, as the census and its consumers name them.
const ( SeverityError = "error" SeverityWarning = "warning" )
Issue severities.
const ( IssueEmptyRow = "empty_row" IssueMissingBlockType = "missing_block_type" IssueInvalidRow = "invalid_row" IssueUnknownBlockType = "unknown_block_type" IssueBlockTypeNotObserved = "block_type_not_observed_here" IssueUnknownField = "unknown_field" IssueValueNotInEnum = "value_not_in_enum" IssueLikelyRequired = "likely_required" IssueRequiredMissing = "required_field_missing" )
Issue codes. They double as write-path warning codes.
const ( KindScalar = "SCALAR" KindObject = "OBJECT" KindInterface = "INTERFACE" KindUnion = "UNION" KindEnum = "ENUM" KindInputObject = "INPUT_OBJECT" KindList = "LIST" KindNonNull = "NON_NULL" )
GraphQL introspection kinds.
const ( DBPostgres = "postgres" DBMongoDB = "mongodb" DBSQLite = "sqlite" DBUnknown = "unknown" )
DB adapter values (§7.11).
const ( // ReachabilityOK means both /api/access and the GraphQL Access type // listed the entity — or, when GraphQL is unavailable, that /api/access // listed it and nothing contradicted that. ReachabilityOK = "ok" // ReachabilityGraphQLDisabled means /api/access saw it and GraphQL did // not. ReachabilityGraphQLDisabled = "graphql-disabled" // ReachabilityAccessDenied means GraphQL saw it and /api/access did not, // i.e. this identity has zero permissions on it. ReachabilityAccessDenied = "access-denied" )
Reachability records which of the two inventory sources saw an entity (§7.3). Neither source alone is complete: /api/access omits payload-kv (zero permission for this identity) and GraphQL omits payload-migrations (endpoints: false), both verified live.
const ( ReasonAccessDenied = "access-denied" ReasonEndpointsDisabled = "endpoints-disabled" ReasonGraphQLDisabled = "graphql-disabled" )
Reasons recorded in manifest.unreachable[].
const ( GraphQLModeOK = "ok" GraphQLModeIntrospectionDisabled = "introspection_disabled" GraphQLModeErrors = "errors" GraphQLModeDisabled = "disabled" GraphQLModeRouteMissing = "route_missing" GraphQLModeUnreachable = "unreachable" )
GraphQL degradation modes (§7.6).
const ( SourceConfigured = "configured" SourceProbed = "probed" SourceGraphQL = "graphql" SourceGraphQLInput = "graphql-input" SourceGraphQLEnum = "graphql-enum" SourceGraphQLArg = "graphql-arg" // SourceGraphQLArgAbsent is §7.8.1's localization.locales_source for "the // plural Query field has no locale argument", i.e. the project is provably // not localised. SourceGraphQLArgAbsent = "graphql-arg-absent" SourceGraphQLEnumMangled = "graphql-enum-mangled" SourceProbe = "probe" SourceAccess = "access" SourceObserved = "observed" SourceLocaleAll = "locale-all" SourceLocaleAllUnverified = "locale-all-unverified" SourceProjectSource = "project-source" SourceProjectPackageJSON = "project-package-json" SourceBulkDeleteMessage = "bulk-delete-message" SourceDerived = "derived" SourceAccessZip = "access-zip" SourceTypeProbe = "type-probe" SourceFuzzy = "fuzzy" SourceBootstrap = "bootstrap" SourceCached = "cached" SourceInferred = "inferred" SourceWriteProbe = "write-probe" SourceUnknown = "unknown" // SourceNA is the documented sentinel for "this key does not apply to // this field", as opposed to "PayCLI could not find out". SourceNA = "n/a" // SourceDerivedGraphQLPath mirrors config.SourceDerivedGraphQL so the two // layers report the same string for the same fact. SourceDerivedGraphQLPath = "derived:api_path+graphql_route" )
Provenance values. Every derived fact is written together with the story of where it came from, or neither is written (§7.8.3).
const ( ConfidenceGraphQL = "graphql" ConfidenceInferred = "inferred" ConfidenceObserved = "observed" ConfidenceHeuristic = "heuristic" ConfidenceMeasured = "measured" ConfidenceUnknown = "unknown" )
Confidence values for payload_type and sortable.
const ( IDTypeNumber = "number" IDTypeString = "string" IDTypeUnknown = "unknown" )
IDType values (§7.6). Unknown is a first-class answer, not an error state.
const ( TypeText = "text" TypeTextarea = "textarea" TypeNumber = "number" TypeCheckbox = "checkbox" TypeDate = "date" TypeEmail = "email" TypeSelect = "select" TypeRadio = "radio" TypeRelationship = "relationship" TypeUpload = "upload" TypeJoin = "join" TypeBlocks = "blocks" TypeRichText = "richText" TypeJSON = "json" TypeGroup = "group" TypeArray = "array" TypePoint = "point" TypeID = "id" TypeUnknown = "unknown" )
Payload field kinds inferred by §7.4's table.
const ( WriteShapeID = "id" WriteShapeIDArray = "id_array" WriteShapeRelValue = "{relationTo,value}" WriteShapeRelList = "[{relationTo,value}]" WriteShapeJSON = "json" )
WriteShape is §7.8.2's closed enum. It is mandatory and non-null on every relationship, upload and polymorphic field and null on every other kind, because §9.10's --set coercion reads exactly this key.
const ( HookMutatedUnknown = "unknown" HookMutatedTrue = "true" HookMutatedFalse = "false" )
HookMutated is tri-state as a string because "unknown" is the normal answer: beforeChange / beforeValidate hooks are not introspectable (§7.6, HOOK_MUTATION_UNKNOWN).
const ( LimBlockSlugsUnknown = "BLOCK_SLUGS_UNKNOWN" LimCustomEndpointsNotEnum = "CUSTOM_ENDPOINTS_NOT_ENUMERABLE" LimSortabilityHeuristic = "SORTABILITY_HEURISTIC" LimLocalizationUnknown = "LOCALIZATION_UNKNOWN" LimRichTextShapeUnknown = "RICHTEXT_SHAPE_UNKNOWN" LimIDTypeUnknown = "ID_TYPE_UNKNOWN" LimCapabilityUnknown = "CAPABILITY_UNKNOWN" LimLocalesUnknown = "LOCALES_UNKNOWN" LimLocalizationPerFieldUnknwn = "LOCALIZATION_PER_FIELD_UNKNOWN" LimBlockSlugsUnknownNoSource = "BLOCK_SLUGS_UNKNOWN_NO_SOURCE" LimAuthCandidatesTruncated = "AUTH_CANDIDATES_TRUNCATED" LimHookMutationUnknown = "HOOK_MUTATION_UNKNOWN" LimPayloadVersionUnknown = "PAYLOAD_VERSION_UNKNOWN" LimDBAdapterUnknown = "DB_ADAPTER_UNKNOWN" )
Limitation codes (§7.6). They are stable identifiers: an agent branches on the code, never on the prose.
const ( StageModeProbe = "mode_probe" Stage1 = "stage1" Stage2 = "stage2" Stage3 = "stage3" StageFingerprint = "fingerprint" )
Stage names recorded in diagnostics.degraded[].
const BlockDocsNote = "label and description are the project's OWN words, read from the block's " +
"config.ts on disk (§7.10): `labels: { singular, plural }` and a description under " +
"custom.description / custom.docs / custom.summary. Payload publishes neither over REST or " +
"GraphQL and defines NO description field for a block at all, so a project gets these only if " +
"its authors wrote them; null means nobody did, and description_key says which key answered."
BlockDocsNote states, once per response, where a block's label and description come from — so that a null is read as "this project did not write one" and never as "PayCLI failed".
const BlockPlumbingNote = "id, blockName and blockType are Payload plumbing, not content fields: " +
"blockType is MANDATORY on every block you write and must be the slug (Payload silently drops a " +
"row whose blockType it does not recognise and still answers 201), blockName is an optional " +
"admin-UI label, and id is server-generated — omit it when creating."
BlockPlumbingNote is the one-line explanation of the three keys Payload puts on every block row. They are protocol, not content, and an agent that treats them as fields to fill in produces nonsense.
const CensusVersion = 1
CensusVersion is the on-disk layout version. A file carrying any other value is a cache miss.
const DefaultCensusMaxDocs = 500
DefaultCensusMaxDocs bounds how many documents one entity's census reads. Pages are paged 100 at a time, so the default costs at most five requests per collection.
const DefaultTTL = 10 * time.Minute
DefaultTTL is the discovery manifest's fresh window (§8.5).
const FallbackNone = "none"
FallbackNone is the value PayCLI sends as fallback-locale on every read when localisation is enabled (§7.9a). sanitizeFallbackLocale maps 'false' | 'none' | 'null' to false, so an untranslated field comes back null/absent instead of masquerading as a translation.
This is not a display preference. config.localization.fallback defaults to true, so without it `pay get pages 1 --locale de` returns English strings for every untranslated field, and an agent that read-modify-writes copies English into de and believes it translated the page.
const LocaleUnverifiedMessage = "" /* 203-byte string literal not displayed */
LocaleUnverifiedMessage is §7.9c's message, verbatim. It is verbatim because the behaviour it describes is invisible: an unknown locale code is silently coerced to the default locale with HTTP 200 and no warning.
const LocaleWarnUnverified = "locale_unverified"
LocaleWarnUnverified is the warning code attached when --locale could not be validated client-side.
const ManifestVersion = 2
ManifestVersion is §7.8.1's layout version. It is 2 because the single consolidated manifest of the first draft was split into an index plus field shards; a manifest_version: 1 file on disk is a cache miss, not a parse error.
const MaxLearnedOperatorFailures = 64
MaxLearnedOperatorFailures bounds the memo. It is a cache file an agent may never garbage-collect by hand, and a project with more than this many distinct failing operator/collection pairs has a bigger problem than the memo can express; the oldest record is dropped first.
const SourceCensusMatched = "census-matched"
SourceCensusMatched labels a blockType slug the census joined to a GraphQL union member by name because introspection published only the member's interfaceName (CallToActionBlock) and a real document carries the slug (cta).
const SourceMixed = "mixed"
SourceMixed is a field whose slugs did not all come from the same place. It never appears alone: SlugSources always says which slug came from where.
const SourceObservedCensus = "observed-census"
SourceObservedCensus labels every fact the census measured. It is distinct from SourceObserved (the three-document REST field sample) because the two differ by two orders of magnitude in evidence.
const SourceObservedDepth1 = "observed-depth-1"
SourceObservedDepth1 labels a relationship/upload kind learned by reading one document at depth 1, where a relationship id comes back as a whole document.
const SourcePayloadProtocol = "payload-protocol"
SourcePayloadProtocol is the provenance of a fact that comes from Payload's block wire format itself rather than from this project: every block row carries id/blockName/blockType, and blockType must be sent. It is a distinct source because it is true of every Payload 3.x project and was not measured on this one.
const SourceUnionInferred = "inferred-from-interface-name"
SourceUnionInferred is the provenance of a slug derived from a GraphQL interfaceName because the project source had no pair for it — which is how every plugin-provided block arrives, since node_modules is deliberately not scanned. It is deliberately distinct from SourceProjectSource so an agent can tell a confirmed slug from an inferred one at a glance.
Variables ¶
var BlockFieldSchemaKeys = []string{
"name", "path", "parent",
"payload_type", "payload_type_confidence", "payload_type_source",
"graphql_type", "json_type", "has_many",
"required", "required_source",
"options", "options_source",
"relation_to", "relation_to_source", "polymorphic",
"write_shape",
"plumbing",
"label",
"description", "description_source", "description_key",
}
BlockFieldSchemaKeys is the exact key set every block field entry carries, exported so a consumer can assert the contract instead of re-deriving it.
var FieldKeys = []string{
"name", "path", "graphql_path", "parent",
"payload_type", "payload_type_confidence",
"graphql_type", "json_type",
"required", "required_source",
"has_many", "localized", "localized_source",
"read_only",
"options", "options_source",
"relation_to", "relation_to_source", "polymorphic",
"write_shape",
"queryable", "operators",
"sortable", "sortable_confidence",
"hook_mutated",
"label",
}
FieldKeys is the exact, ordered key set every field entry carries. It is exported so a consumer can assert the contract instead of re-deriving it.
Functions ¶
func AttributeOperatorFailure ¶
AttributeOperatorFailure implements the test described above. It returns the operator to blame and true only when the failure is genuinely attributable.
serverFailed is the caller's verdict that the server, not PayCLI, refused the request: an HTTP 500 or a GraphQL QueryError. operators is every distinct operator the request carried.
func BlockSchemasForShard ¶ added in v0.2.0
func BlockSchemasForShard(shard *Shard, schema *Schema, src BlockSources) map[string]BlockTypeSchema
BlockSchemasForShard builds one schema per distinct blockType slug reachable from any of the shard's blocks fields.
Keyed by SLUG because that is what an agent writes, and because a block type used by two fields is the same type both times: verified that pages.layout and posts.layout share cta, content and mediaBlock.
func CensusDocKey ¶ added in v0.3.0
CensusDocKey is the census's key for a document id: its decimal or string form, and "_global" for a global's one document (id nil).
func ClosestSlug ¶ added in v0.3.0
ClosestSlug is the pool entry a mistyped slug most plausibly meant (the validator's did-you-mean), or "" when none is close enough.
func CompatibleTypes ¶ added in v0.3.0
func CompatibleTypes(a, b *CensusType) bool
CompatibleTypes reports whether two observations of one slug describe the same block: at least 80% of the union of their row keys is shared.
func ContainsArray ¶ added in v0.3.0
ContainsArray reports whether a value holds a non-empty array anywhere outside rich text: the cheap test for "could this body touch a blocks field at all", made before any block knowledge is loaded.
func ContainsBlockRows ¶ added in v0.3.0
ContainsBlockRows reports whether a value holds a blocks array anywhere — the trigger for block validation on a write body.
func ContainsContent ¶ added in v0.3.0
ContainsContent reports whether a value has content: not null, not "", not [], not {}, and not an empty rich-text value.
func CrossValidateLocales ¶
CrossValidateLocales is §7.9b step 2's GraphQL check: accept the locale=all candidates only when their count matches LocaleInputType's and every candidate formatName()s to an enum name.
func DefaultRichTextValue ¶ added in v0.3.0
DefaultRichTextValue returns a fresh copy of Payload's own empty rich-text value — @payloadcms/richtext-lexical's defaultRichTextValue (3.87, dist/populateGraphQL/defaultValue.js): one paragraph holding one empty text node. Payload's hasText() reports it as empty, so a required rich-text field still fails validation until it is filled in.
func DescribeBlocksHelp ¶
DescribeBlocksHelp is the text `pay describe <c> --field <f>` prints for an unresolved blocks field, verbatim from §7.10. It names only sources that can actually answer: no hint in PayCLI may name a `pay` command that cannot.
func FlagsFromObject ¶
FlagsFromObject reads the three capability flags that Payload encodes as fields of the object type. This is strictly better evidence than §7.5's REST probes — it is the schema rather than an inference from a 400 — and it is what keeps §7.1's four-request cold budget intact on a project whose GraphQL is reachable.
_status => drafts are enabled (verified: Page has it, Category does not) deletedAt => trash is enabled (verified: CrmContact has it, Page does not) folder => folders are enabled (verified: Media has it, Page does not)
func FormatName ¶
FormatName reproduces Payload's formatName(): every character in -./+,()'[] and space becomes an underscore. It is what turns the slug `crm-contacts` into the Access field `crm_contacts`, and it is the only direction that is safe to compute — the inverse is ambiguous.
func HashShard ¶
HashShard is the content hash written to fields_sha256. The hash covers the fields and their provenance but not the generation or the hash slot itself, so an unchanged schema keeps a stable hash across runs and §8.4's staleness check cannot be defeated by a new generation id.
func HumanizeField ¶
HumanizeField turns a camelCase or snake_case field name into an admin-ish label. It is a display aid only: Payload's real labels are not introspectable per field, and nothing behavioural keys off this string.
func IDString ¶
IDString renders an observed id for the adapter inference without going through fmt, so a json.Number keeps its exact digits.
func IDTypeFromArg ¶
func IDTypeFromArg(f *IntroField) (string, bool)
IDTypeFromArg reads §7.3's authoritative signal: the `id` argument of the singular Query field. Int means a relational adapter's integer primary key, String means Mongo ObjectIds or UUIDs.
This matters beyond validation: passing a non-castable id causes 500s, and on /duplicate it caused unintended writes.
func IDTypeOfJSON ¶
IDTypeOfJSON types a decoded JSON id value. json.Number and float64 are both accepted because a decoder configured with UseNumber and one without must agree.
func InferDBAdapter ¶
InferDBAdapter is §7.11 source 3: the only inference the API supports, and it is deliberately weak. It never unlocks §9.3's client-side unsupported_operator block, which requires db_adapter_source == "configured" — because on MongoDB `all` maps to $all and works, so a guessed adapter would make PayCLI refuse a query the server would have answered.
idTypes are the resolved id_types of every collection; sampleIDs are any observed string ids.
func IsComplexityError ¶
IsComplexityError reports §7.4's halve-the-batch trigger.
func IsInternal ¶
IsInternal reports Payload's own collections, which §2's ground truth says to de-emphasise in default listings but keep reachable.
func IsJoinType ¶
IsJoinType reports §7.4's "OBJECT with exactly {docs, hasNextPage, totalDocs}". The check is on the key set, not on the type name, because the name is {T}_{field} and a group field can share that shape of name.
func IsRelationshipType ¶
IsRelationshipType reports §7.4's polymorphic wrapper: an OBJECT whose only fields are relationTo and value.
func IsUploadType ¶
IsUploadType reports whether an OBJECT type carries all four upload markers.
func LinkLikeValue ¶ added in v0.3.0
LinkLikeValue reports a string that looks like a URL, path, anchor, colour, e-mail address or file/domain name rather than an identifier. A ratio such as "2/3" is an identifier: only a LEADING slash makes a path.
func LocaleEnumNames ¶
LocaleEnumNames reads LocaleInputType's values. They are formatName()-mangled (en-US is exposed as en_US) and introspection never exposes the underlying value, so they are used for cross-validation and for locale_count only — never as codes (§7.9).
func LocaleUnverifiedHint ¶
LocaleUnverifiedHint is §7.9c's hint, verbatim modulo the profile name.
func LocalesValidatable ¶
LocalesValidatable reports §7.9c: --locale is checked client-side only when the codes are configured or were cross-validated from a locale=all sample. Anything else is passed through with the locale_unverified warning.
func LooksNumeric ¶
LooksNumeric reports whether a raw command-line id is all digits, used by callers that must decide whether a string id is safe to send to a number-typed collection.
func Mitigation ¶
Mitigation returns §7.6's recorded mitigation for a limitation code.
func NewBlockRow ¶ added in v0.3.0
func NewBlockRow(slug string, stats *CensusType, gql *BlockTypeSchema) (map[string]any, []string)
NewBlockRow builds a ready-to-edit row for a blockType: every commonly present field, the most common value of an enum-like or boolean field, an empty rich-text value, [] for nested blocks and arrays, null for everything else. stats may be nil (GraphQL-only knowledge); gql may be nil (census only). It returns the fields it filled from an observed value, so the caller can say which values are defaults it chose.
func NormalizeBlockPath ¶ added in v0.3.0
NormalizeBlockPath turns any concrete or addressed blocks path into the census's canonical PATTERN: every index or selector becomes `[]`.
layout -> layout layout.2.blocks -> layout[].blocks layout[2].blocks -> layout[].blocks layout[id:6ab…].blocks -> layout[].blocks layout[type:group[1]].blocks-> layout[].blocks layout[].blocks[].blocks -> layout[].blocks[].blocks
A path the census itself printed is returned unchanged.
func ObservedBlockTypes ¶ added in v0.2.0
ObservedBlockTypes harvests the blockType values actually present in sampled documents, keyed by the dotted field path they appeared at.
This is the only source that is both per-field and evidence-based, so it is what keeps the REST-only path (no GraphQL, therefore no union) from having to guess. It reports what was SEEN, never what is allowed: the caller must not present it as a complete list.
func OperatorFailureCandidate ¶
OperatorFailureCandidate reports whether an operator is one §7.11 will ever learn about. Callers use it to skip the memo lookup for the ninety per cent of queries that only use equals/greater_than/contains.
func OperatorsFor ¶
OperatorsFor returns the operators Payload accepts for a field kind. A polymorphic relationship's where entry is a {T}_{f}_Relation input object taking relationTo and value rather than an operator set, which is why the operator type name is inspected as well as the kind.
func ParseDeleteMessage ¶
ParseDeleteMessage extracts the plural label from a bulk-delete message. The second result is false for a translated, reworded or custom message.
func ResolveIDType ¶
func ResolveIDType(in IDLadderInput) (idType, source string)
ResolveIDType walks §7.6's ladder, stopping at the first hit. The last rung is "unknown" — reachable on purpose, because a defaulted "number" would make PayCLI reject every valid 24-hex ObjectId with an error message that is a lie, and a defaulted "string" would silently lose the Postgres protection the check exists for.
func ResolveLocales ¶
func ResolveLocales(in LocaleInput) (locales []string, source string, count *int)
ResolveLocales walks §7.9b, stopping at the first hit. The mangled GraphQL enum names are deliberately not returned as codes: en-US is exposed as en_US and es.419 as es_419, and introspection never exposes the underlying value, so codes harvested that way would be rejected by PayCLI's own validation if fed back.
func ResolveShardBlocks ¶ added in v0.2.0
func ResolveShardBlocks(shard *Shard, schema *Schema, src BlockSources)
ResolveShardBlocks resolves EVERY blocks field of a shard, one field at a time, and records the answer on the shard.
The per-field union is read from the field's own GraphQL type (Page_Layout, Form_Fields, …) rather than from a project-wide list, which is the whole point: the project source scan cannot know which collection a block belongs to, so attaching its bag to every blocks field made `pay describe forms` advertise page layout blocks for a form-builder field.
It is idempotent and safe to run over an already-resolved shard; the caller must call Finalize afterwards, because the answer is part of the shard hash.
func RouteMissingHint ¶
RouteMissingHint is §7.6's path-specific hint. It fires only when the mode is route_missing, graphql_path was derived rather than set, and api_path is not the default — the exact situation in which a working GraphQL endpoint is being missed.
func SchemaSHA256 ¶
SchemaSHA256 is §8.4's Level-2 fingerprint: sha256 over the sorted field names of {__type(name:"Query"){fields{name}}}. It is byte-identical authenticated and unauthenticated, which makes it the only permission-free schema-version proxy the API offers.
func SlugFromInterfaceName ¶ added in v0.2.0
SlugFromInterfaceName runs Payload's GraphQL type naming backwards for a block whose slug PayCLI could not read from project source.
Payload names a block's union member after its interfaceName when one is declared and after the PascalCased slug otherwise, so lower-casing the first letter recovers the slug for every single-word and camelCase slug. Verified against @payloadcms/plugin-form-builder, whose nine Form_Fields members Checkbox, Country, Email, Message, Number, Select, State, Text and Textarea invert to exactly the nine slugs it declares (checkbox, country, email, message, number, select, state, text, textarea), and against a live form document whose fields[].blockType values are text, email and textarea.
It refuses the cases it cannot invert rather than guessing: an acronym run (FAQBlock) would yield fAQBlock and a kebab-case slug's PascalCase is not recoverable at all. Returning "" there makes the member show up in BlockResolution.Unresolved, which is the honest answer.
func SortableKind ¶
SortableKind reports §7.5's sortability heuristic. It is a heuristic, not a measurement, which is exactly why every field records sortable_confidence: "heuristic" and the manifest carries SORTABILITY_HEURISTIC.
func TitleField ¶
TitleField picks the field `pay find` shows as a document's human label. It is a display aid with no behavioural consequence, so a heuristic is honest here in a way it would not be for a capability.
func TitleFromSlug ¶
TitleFromSlug turns a slug into a title-cased label.
func TopologySHA256 ¶
func TopologySHA256(p TopologyProjection) string
TopologySHA256 is §8.4's Level-1 fingerprint. It detects a collection or global being added or removed, versions being enabled (readVersions appears) and access-control changes for this identity — for ~14 ms and 30 KB.
It cannot detect a field being added to an existing collection, because `fields` collapses to the boolean true for a privileged key. That blind spot is stated honestly here and is what Level 3 exists for.
func TouchesBlocks ¶ added in v0.3.0
func TouchesBlocks(doc map[string]any, k *BlockKnowledge) bool
TouchesBlocks reports whether a document or write body holds anything the block check must look at: a block row anywhere (ContainsBlockRows), or a non-empty array at a position k knows is a blocks field — a census path with rows, or a top-level field the schema declares as blocks. The second half is what catches `layout=[{}]` and `layout=[{"heading":"x"}]`: no row carries a blockType, so Payload drops them all with a 2xx and the field becomes [], wiping the layout.
func UnionMembers ¶ added in v0.2.0
UnionMembers returns the possibleTypes of a field's GraphQL type when that type is a union, and nil otherwise. nil means "the API published no union for this field", which is a genuinely different answer from "the union is empty" and is what sends ResolveBlocks down its non-GraphQL ladder.
func UnresolvedBlocksReason ¶
UnresolvedBlocksReason is §7.10's publishable_reason, verbatim.
func UploadFromObject ¶
UploadFromObject reports §7.4's upload signal on the entity's own type.
func UseAPIKey ¶
UseAPIKey reports §7.3's use_api_key signal: the mutation input type carries both apiKey and enableAPIKey (verified live on mutationUserInput). It drives §5.4's `pay doctor` reporting and nothing else.
func ValueAtRowPath ¶ added in v0.3.0
ValueAtRowPath reads a concrete census path (`layout[1].blocks[0]`, `hero.items[2]`) out of a document.
func WriteShapeFor ¶
WriteShapeFor implements §7.8.2's closed enum. It is the only producer of the key, so a relationship can never be written without one.
Types ¶
type AcceptedSet ¶ added in v0.3.0
type AcceptedSet struct {
Path string `json:"path"`
// Parent is the owning blockType for a parent-scoped pattern
// (`group.blocks`), empty otherwise.
Parent string `json:"parent,omitempty"`
Types []AcceptedType `json:"types"`
// Authoritative is true only when a declared source enumerates the whole
// set: a profile pin, or a GraphQL union whose every member resolved to a
// confirmed slug. Only then is "not in the set" an error rather than
// "never observed here".
Authoritative bool `json:"authoritative"`
Source string `json:"source"`
// InterfaceNames and Unconfirmed carry the GraphQL half when there is one.
InterfaceNames []string `json:"interface_names,omitempty"`
Unconfirmed []string `json:"unconfirmed,omitempty"`
Reason string `json:"reason,omitempty"`
// Rows/Docs/EmptyRows/UntypedRows are the census counts at this path.
Rows int `json:"rows"`
Docs int `json:"docs"`
EmptyRows int `json:"empty_rows"`
UntypedRows int `json:"untyped_rows"`
}
AcceptedSet is what one blocks-field path pattern takes.
func (*AcceptedSet) Has ¶ added in v0.3.0
func (a *AcceptedSet) Has(slug string) bool
Has reports whether slug is in the set.
func (*AcceptedSet) Slugs ¶ added in v0.3.0
func (a *AcceptedSet) Slugs() []string
Slugs returns the set's slugs in order.
type AcceptedType ¶ added in v0.3.0
type AcceptedType struct {
Slug string `json:"slug"`
// Count is how many rows of this type the census saw at this path.
Count int `json:"count"`
Sources []string `json:"sources"`
InterfaceName string `json:"interface_name,omitempty"`
Label *string `json:"label,omitempty"`
Description *string `json:"description,omitempty"`
}
AcceptedType is one blockType a blocks field takes.
type AuthBootstrap ¶
type AuthBootstrap struct {
// Collection is the resolved auth-collection slug.
Collection string
// Source is "bootstrap" when Stage -1 ran, "configured" when it was
// skipped because the slug was set, "cached" when it came from
// auth-resolution.json.
Source string
// Candidates are every slug that survived the /init filter, in the order
// they were tried. They are recorded in diagnostics.auth_candidates[].
Candidates []string
// Truncated reports that the candidate set was the anonymous slug list
// alone.
Truncated bool
// Requests is the number of HTTP requests Stage -1 spent.
Requests int
}
AuthBootstrap is Stage -1's result (§7.0).
type BlockDoc ¶ added in v0.2.0
type BlockDoc struct {
Slug string `json:"slug"`
Label *string `json:"label"`
LabelPlural *string `json:"label_plural"`
// LabelsSource / DescriptionSource are "project-source" or "unknown".
// Nothing here is ever derived from the slug: a block whose author wrote
// no labels has none, and title-casing "mediaBlock" would invent one.
LabelsSource string `json:"labels_source"`
Description *string `json:"description"`
DescriptionSource string `json:"description_source"`
// DescriptionKey names the config key the text came from, because Payload
// defines none for a block: see BlockTypeSchema.DescriptionKey.
DescriptionKey string `json:"description_key"`
// FieldsCount is the block's content fields, excluding the three plumbing
// keys every block row carries.
FieldsCount int `json:"fields_count"`
// DocsReason is empty exactly when both label and description were found,
// and otherwise says why they were not — a plugin block has no config on
// disk, and that is expected rather than a failure.
DocsReason string `json:"docs_reason"`
}
BlockDoc is the CHOOSING view of one block type: the human name, what the block is for, and how many fields it has — enough to pick between cta, content and mediaBlock without running a second command, and small enough to print beside every slug list.
It is a projection of BlockTypeSchema, never a second source of truth: every value here is copied from the schema that `--block <slug>` prints in full.
type BlockField ¶ added in v0.2.0
type BlockField struct {
Path string `json:"path"`
// Slugs are the blockType values the REST API accepts here, or null when
// none could be resolved.
Slugs []string `json:"slugs"`
// Source is the weakest provenance among Slugs, or "mixed".
Source string `json:"source"`
// SlugSources gives every slug its own provenance so a confirmed slug is
// distinguishable from one inferred from an interfaceName.
SlugSources map[string]string `json:"slug_sources"`
// SlugInterfaces pairs each slug with the GraphQL union member it came
// from, which is how a slug is joined to the block's field schema in
// Shard.BlockSchemas.
SlugInterfaces map[string]string `json:"slug_interface_names"`
// InterfaceNames is the field's GraphQL union possibleTypes, verbatim.
// They are NOT blockType slugs and must never be sent to the API.
InterfaceNames []string `json:"interface_names"`
// Unresolved lists union members no source could name; the field accepts
// them but PayCLI cannot say what to call them.
Unresolved []string `json:"unresolved_interface_names"`
// Reason is empty exactly when every slug came from a confirmed source.
Reason string `json:"reason"`
}
BlockField is one blocks field's resolved answer, recorded per field because there is no such thing as a project-wide blockType list: verified live that Page.layout accepts 5 block types and Form.fields accepts 9 entirely different ones.
func DeclaredBlockField ¶ added in v0.3.0
func DeclaredBlockField(shard *Shard, path string) (BlockField, bool)
DeclaredBlockField returns a top-level blocks field's declared answer from a shard: its block_fields entry, or — for a shard written before block_fields existed — the legacy blocks/blocks_source pair.
type BlockFieldSchema ¶ added in v0.2.0
type BlockFieldSchema struct {
Name string `json:"name"`
// Path is dotted WITHIN the block: "links.link.url". It is not a
// collection field path and cannot be used in --where.
Path string `json:"path"`
Parent *string `json:"parent"`
PayloadType string `json:"payload_type"`
PayloadTypeConfidence string `json:"payload_type_confidence"`
// PayloadTypeSource is "graphql" unless the block's config.ts resolved an
// ambiguity GraphQL cannot: lexical richText and json are the same JSON
// scalar, and select and radio compile to the same ENUM.
PayloadTypeSource string `json:"payload_type_source"`
GraphQLType *string `json:"graphql_type"`
JSONType string `json:"json_type"`
HasMany bool `json:"has_many"`
// Required is tri-state and is null far more often here than on a
// collection field: see this file's header for why.
Required *bool `json:"required"`
RequiredSource string `json:"required_source"`
Options []string `json:"options"`
OptionsSource string `json:"options_source"`
RelationTo []string `json:"relation_to"`
RelationToSource string `json:"relation_to_source"`
Polymorphic bool `json:"polymorphic"`
WriteShape *string `json:"write_shape"`
// Plumbing marks id/blockName/blockType — Payload's own keys, not this
// block's content.
Plumbing bool `json:"plumbing"`
Label string `json:"label"`
// Description is the field's own human instruction in the project's own
// words, read from `admin: { description: '…' }` in the block's config.ts
// (§7.10). It is a POINTER: null is "nobody wrote one", which is a
// different fact from an empty string and must never be filled in with a
// sentence PayCLI made up.
Description *string `json:"description"`
// DescriptionSource is "project-source" when Description is non-null and
// "unknown" when it is null. A plugin's block lives in node_modules, which
// §7.10 never scans, so every one of its fields reads "unknown" — expected,
// and stated rather than hidden.
DescriptionSource string `json:"description_source"`
// DescriptionKey is the literal config key the text came from
// ("admin.description"), "" when there is none. Payload has no standard
// description for a BLOCK, so naming the key is what keeps this output
// honest about where the words originated.
DescriptionKey string `json:"description_key"`
}
BlockFieldSchema is ONE field inside a block type.
It is deliberately not §7.8.2's Field: `queryable`, `operators`, `sortable` and `localized` are measured against a collection's `{Singular}_where` and a `locale=all` sample, neither of which exists for a block's interior, and emitting them here would mean publishing four confident-looking values that were never measured.
type BlockFieldView ¶ added in v0.3.0
type BlockFieldView struct {
// Path is the field's path inside the row: `title`, `section.contentWidth`,
// `buttons[].label`.
Path string `json:"path"`
Name string `json:"name"`
Depth int `json:"depth"`
Kind string `json:"kind"`
KindSource string `json:"kind_source"`
JSONTypes []string `json:"json_types"`
// PayloadType is the declared type when GraphQL or project source has one.
PayloadType *string `json:"payload_type"`
// Observed is how many rows (or group/array entries) this field's
// percentages are relative to.
Observed int `json:"observed"`
PresencePct int `json:"presence_pct"`
NonNullPct int `json:"non_null_pct"`
Values []CensusValue `json:"values"`
EnumLike bool `json:"enum_like"`
Options []string `json:"options"`
OptionsSource string `json:"options_source"`
// Required is "true", "false", "likely" or "unknown".
Required string `json:"required"`
RequiredSource string `json:"required_source"`
Accepts []string `json:"accepts,omitempty"`
RelationTo []string `json:"relation_to,omitempty"`
Plumbing bool `json:"plumbing"`
Description *string `json:"description"`
Sources []string `json:"sources"`
}
BlockFieldView is one row of a block's field table.
type BlockIssue ¶ added in v0.3.0
type BlockIssue struct {
Severity string `json:"severity"`
Code string `json:"code"`
Path string `json:"path"`
BlockType string `json:"block_type,omitempty"`
Message string `json:"message"`
Hint string `json:"hint,omitempty"`
// Source names the fact that produced the issue: a declared source
// (configured, project-source, graphql) or observed-census.
Source string `json:"source"`
}
BlockIssue is one finding.
type BlockKnowledge ¶ added in v0.3.0
type BlockKnowledge struct {
Kind string
Slug string
// Entity is this entity's census, nil when none was read.
Entity *CensusEntity
// Census is the whole census, used for cross-entity facts about a block
// type (a block used on pages and posts is the same block).
Census *Census
// Shard is the entity's field schema, nil when discovery had none.
Shard *Shard
// contains filtered or unexported fields
}
BlockKnowledge is the merged block knowledge for ONE entity.
func NewBlockKnowledge ¶ added in v0.3.0
func NewBlockKnowledge(kind, slug string, census *Census, shard *Shard) *BlockKnowledge
NewBlockKnowledge builds the merged view for one entity.
func (*BlockKnowledge) Accepted ¶ added in v0.3.0
func (k *BlockKnowledge) Accepted(pattern string) *AcceptedSet
Accepted answers "what may go at this pattern". pattern is canonical (NormalizeBlockPath) or parent-scoped (`group.blocks`).
func (*BlockKnowledge) GraphQLSchema ¶ added in v0.3.0
func (k *BlockKnowledge) GraphQLSchema(slug string) (BlockTypeSchema, bool)
GraphQLSchema returns the declared block schema for a (census) slug.
func (*BlockKnowledge) HasObservedBlocks ¶ added in v0.3.0
func (k *BlockKnowledge) HasObservedBlocks() bool
HasObservedBlocks reports whether this entity's census saw at least one block row. Without that, "never observed" says nothing about the entity: it was not read, or the read stopped on documents that hold no blocks.
func (*BlockKnowledge) KnownTypes ¶ added in v0.3.0
func (k *BlockKnowledge) KnownTypes() []string
KnownTypes lists every blockType this entity can name: census, then shard.
func (*BlockKnowledge) ProjectTypes ¶ added in v0.3.0
func (k *BlockKnowledge) ProjectTypes() map[string]bool
ProjectTypes lists every blockType observed anywhere in the census.
func (*BlockKnowledge) ShardSlug ¶ added in v0.3.0
func (k *BlockKnowledge) ShardSlug(slug string) string
ShardSlug returns the slug the shard knows a census slug by.
func (*BlockKnowledge) TopLevelPaths ¶ added in v0.3.0
func (k *BlockKnowledge) TopLevelPaths() []string
TopLevelPaths lists the entity's blocks fields: every path the schema declares plus every top-level pattern the census observed.
func (*BlockKnowledge) TypeStats ¶ added in v0.3.0
func (k *BlockKnowledge) TypeStats(slug string) *CensusType
TypeStats returns what the census observed about a blockType in THIS entity. Only when this entity has never used the type does it fall back to other entities — and then only to the ones whose rows have the same shape, because a slug is not globally unique: the form-builder's `text` field block (name, label, width, required) and a page's `text` content block share a slug and nothing else.
type BlockResolution ¶
type BlockResolution struct {
// Slugs are the blockType values the REST API accepts for this field, or
// nil when nothing could resolve them. nil and empty mean different
// things: nil is "PayCLI does not know".
Slugs []string
// Source is how THIS field was resolved: the weakest provenance among its
// slugs, so a single inferred member downgrades the whole field.
Source string
// SlugSources gives each returned slug its own provenance, so an agent can
// tell a slug confirmed from project source apart from one inferred from
// an interfaceName.
SlugSources map[string]string
// SlugInterfaces pairs each slug with the GraphQL union member it was
// resolved from. It is what makes a block's own field schema reachable:
// the fields are published under the interfaceName (CallToActionBlock)
// while everything writable is keyed by the slug (cta), and without the
// pair the two halves cannot be joined.
SlugInterfaces map[string]string
// Interfaces is the field's GraphQL union possibleTypes, verbatim. They
// are reported, never returned as slugs: feeding an interfaceName back to
// the API is exactly the silent-wrong-answer this type exists to prevent.
Interfaces []string
// Unresolved are union members no source could name, i.e. slugs that are
// missing from Slugs even though the API accepts them.
Unresolved []string
// Reason is the plain-words explanation of why the answer is less than
// confirmed. It is empty only when every slug came from a confirmed
// source; when Slugs is nil it is §7.10's publishable_reason.
Reason string
}
BlockResolution is the answer for ONE blocks field. Every answer is per-field: there is no such thing as a project-wide blockType list, and pretending otherwise is what made `pay describe forms` advertise page layout blocks for a form-builder field.
func ResolveBlocks ¶
func ResolveBlocks(fieldPath string, interfaces []string, src BlockSources) BlockResolution
ResolveBlocks answers ONE blocks field.
interfaces are that field's union possibleTypes (interfaceNames) and are the AUTHORITATIVE SET of what the field accepts — Page_Layout has five members and Form_Fields has nine completely different ones, so no project-wide list can be correct for both. The order is:
- the profile's pin for this field path
- the field's own GraphQL union, each member resolved to a slug
- blockType values observed in this field's sampled documents
- unknown, with a reason
type BlockSchemaView ¶ added in v0.3.0
type BlockSchemaView struct {
BlockType string `json:"block_type"`
InterfaceName string `json:"interface_name,omitempty"`
Label *string `json:"label"`
Description *string `json:"description"`
Count int `json:"count"`
Docs int `json:"docs"`
UsedIn []BlockUse `json:"used_in"`
Sources []string `json:"sources"`
Fields []BlockFieldView `json:"fields"`
Required []string `json:"required_fields"`
Likely []string `json:"likely_required"`
BestInstance *CensusRef `json:"best_instance"`
}
BlockSchemaView is `pay blocks schema <type>`.
func BuildSchemaView ¶ added in v0.3.0
func BuildSchemaView(slug string, stats *CensusType, gql *BlockTypeSchema, uses []BlockUse) BlockSchemaView
BuildSchemaView merges a type's census profile with its declared schema. stats or gql may be missing, never both.
type BlockSourceDecl ¶ added in v0.2.0
type BlockSourceDecl struct {
File string
Fields []BlockSourceField
Complete bool
// LabelSingular and LabelPlural are the block's `labels:` literals. They
// are the human name of the block — "Call to Action" for slug cta — and
// are "" when the project declares none, never a title-cased guess.
LabelSingular string
LabelPlural string
// Description is what this block is FOR, in the project's own words, and
// DescriptionKey is the config key it was read from. Payload defines NO
// standard description for a block, so the key is always reported with the
// text: see config.DescriptionKeys.
Description string
DescriptionKey string
}
BlockSourceDecl is one block's declaration as it exists on disk.
Complete is the load-bearing flag: CallToAction's fields array contains `linkGroup({…})`, a helper call the scanner cannot expand, so `links` is absent from Fields even though the block has it. Only a Complete declaration may answer "false" for a field it does not list; an incomplete one answers "unknown", which is the difference between a fact and a guess.
func (BlockSourceDecl) Field ¶ added in v0.2.0
func (d BlockSourceDecl) Field(name string) (BlockSourceField, bool)
Field returns the declaration for a field name.
type BlockSourceField ¶ added in v0.2.0
type BlockSourceField struct {
Name string
Type string
Required *bool
// Description is the field's own `admin: { description: '…' }` — the
// per-field instruction an agent needs while filling that field in
// ("Pass a media document id"). "" is "the project's authors wrote none".
Description string
// DescriptionKey is the literal config path the text came from,
// "admin.description" in the normal case.
DescriptionKey string
}
BlockSourceField is one field declaration read out of a block's config.ts. Required is nil when the literal was absent or not a boolean.
type BlockSources ¶
type BlockSources struct {
// Configured is the profile's blocks map, keyed by field path. A pin is an
// explicit statement by the operator and outranks everything.
Configured map[string][]string
// ProjectSource is §7.10's local filesystem scan result — every slug:
// literal found in an object that also has fields:. It is a PROJECT-WIDE
// bag with no field attribution, so it is never the answer for a field on
// its own; it only supplies the vocabulary SlugByInterface indexes.
ProjectSource []string
// ProjectSourceFiles are the absolute paths each slug came from, recorded
// in diagnostics.block_slug_files[].
ProjectSourceFiles []string
// SlugByInterface maps an interfaceName declared in project source to the
// slug declared beside it in the same object literal. This is what turns a
// per-field union member into a writable blockType.
SlugByInterface map[string]string
// Observed are blockType values seen in sampled documents, keyed by field
// path. On a fresh project this yields nothing — verified that all 12 live
// pages have layout: [].
Observed map[string][]string
// SourceDecls are the block declarations §7.10's scan read off disk,
// keyed by slug. They carry the one fact GraphQL cannot supply for a block
// — `required: true` — because Payload publishes no input type for a block
// type. A slug absent from this map is a block PayCLI has no source for,
// which is the normal case for every plugin-provided block.
SourceDecls map[string]BlockSourceDecl
}
BlockSources are the candidate origins of a blocks field's real blockType slugs, in §7.10's resolution order.
GraphQL is authoritative about WHICH blocks a field accepts and useless about what they are CALLED on the wire: verified live that __type(name:"Page_Layout").possibleTypes is exactly CallToActionBlock, ContentBlock, MediaBlock, ArchiveBlock, FormBlock while the slugs the REST API accepts are cta, content, mediaBlock, archive, formBlock. The project's own source has the other half — src/blocks/CallToAction/config.ts declares slug: 'cta' beside interfaceName: 'CallToActionBlock' — so the two are only useful together.
type BlockTypeSchema ¶ added in v0.2.0
type BlockTypeSchema struct {
Slug string `json:"slug"`
// InterfaceName is the GraphQL union member this schema was read from. It
// is NOT writable — sending it as a blockType is the §7.10 mistake.
InterfaceName string `json:"interface_name"`
// Fields are the block's own fields plus its three plumbing keys, in
// GraphQL order, with nested group/array interiors flattened into dotted
// paths.
Fields []BlockFieldSchema `json:"fields"`
// FieldsSource is "graphql" when the object type was readable and
// "unknown" when it was not.
FieldsSource string `json:"fields_source"`
// RequiredSource is the summary across the block's content fields: the one
// source when every field agreed, "mixed" when they did not (including
// when SOME field is unknown), and "unknown" when nothing answered for any
// of them. RequiredUnknown and Reason name the gaps precisely.
RequiredSource string `json:"required_source"`
// RequiredUnknown names every content field whose required is null, so an
// agent can see the gap without walking the list.
RequiredUnknown []string `json:"required_unknown"`
// ConfigFile is the absolute path required-ness was read from, or "" when
// no project source declared this slug.
ConfigFile string `json:"config_file"`
// PlumbingNote explains id/blockName/blockType in one line.
PlumbingNote string `json:"plumbing_note"`
// Reason is empty exactly when every content field's required-ness is
// known; otherwise it says in plain words what is missing and why.
Reason string `json:"reason"`
// Label and LabelPlural are the block's human name, read from its
// `labels: { singular, plural }` (§7.10). They are POINTERS because a
// project that declares none has none: null is the honest answer, and
// title-casing the slug would manufacture a label the admin UI never
// shows.
Label *string `json:"label"`
LabelPlural *string `json:"label_plural"`
// LabelsSource is "project-source" when the labels were read off disk and
// "unknown" when they were not.
LabelsSource string `json:"labels_source"`
// Description is the one-sentence statement of what this block is FOR —
// the fact that lets an agent choose between cta, content and mediaBlock
// without opening the project. null when the project's authors wrote none.
Description *string `json:"description"`
// DescriptionSource is "project-source" or "unknown".
DescriptionSource string `json:"description_source"`
// DescriptionKey is the config key the text was read from —
// "custom.description", "custom.docs" or "custom.summary". It is reported
// because Payload defines NO description for a block: a Block's `admin`
// accepts only components/custom/disableBlockName/group/images/jsx and
// `tsc --noEmit` rejects `admin: { description }` on one, so the text is
// always project convention rather than a standard field, and saying which
// key it came from is the difference between reporting and implying.
DescriptionKey string `json:"description_key"`
// DocsReason is empty exactly when both the label and the description were
// found; otherwise it says in plain words which is missing and why.
DocsReason string `json:"docs_reason"`
}
BlockTypeSchema is one block type's full answer: the slug to write, the GraphQL union member it came from, and every field inside it.
func BuildBlockSchema ¶ added in v0.2.0
func BuildBlockSchema(slug, iface string, schema *Schema, decl BlockSourceDecl, declared bool) BlockTypeSchema
BuildBlockSchema reads ONE block type's field schema from its GraphQL object type, overlaying what the project's own source says about required-ness.
iface is the union member name (CallToActionBlock); slug is what the REST API accepts (cta). Both are needed: the schema is read under one name and written under the other.
func (BlockTypeSchema) ContentFields ¶ added in v0.2.0
func (s BlockTypeSchema) ContentFields() []BlockFieldSchema
ContentFields returns the block's fields minus Payload's plumbing.
func (BlockTypeSchema) DisplayName ¶ added in v0.2.0
func (s BlockTypeSchema) DisplayName() string
DisplayName is the one-line human name for a block: its declared singular label, or the slug itself when the project declared none. It is for display only — the slug is what is written to the API — and it never fabricates a label, which is why the fallback is the slug verbatim rather than a title-cased guess.
func (BlockTypeSchema) Doc ¶ added in v0.2.0
func (s BlockTypeSchema) Doc() BlockDoc
Doc projects a block's schema into the compact form printed beside a slug list.
func (BlockTypeSchema) RequiredFields ¶ added in v0.2.0
func (s BlockTypeSchema) RequiredFields() []string
RequiredFields returns the names of the fields proved required.
type BlockUse ¶ added in v0.3.0
type BlockUse struct {
Kind string `json:"kind"`
Slug string `json:"slug"`
Path string `json:"path"`
Count int `json:"count"`
}
BlockUse is one place a blockType is used.
type BlockValidation ¶ added in v0.3.0
type BlockValidation struct {
CheckedRows int `json:"checked_rows"`
Errors int `json:"errors"`
Warnings int `json:"warnings"`
Issues []BlockIssue `json:"issues"`
}
BlockValidation is the result of one validation pass.
func ValidateBlocks ¶ added in v0.3.0
func ValidateBlocks(doc map[string]any, k *BlockKnowledge) BlockValidation
ValidateBlocks checks every block row in doc. k may carry no facts at all; the structural checks (`{}` rows, rows without a blockType) still run.
func ValidateRowsAt ¶ added in v0.3.0
func ValidateRowsAt(rows []any, field string, k *BlockKnowledge) BlockValidation
ValidateRowsAt checks bare block rows as if they sat in the blocks field at field: a top-level field (`layout`), a nested position in any grammar NormalizeBlockPath accepts (`layout[].blocks`, `layout.2.blocks`, `layout[type:group[1]].blocks`), or a parent-scoped field (`group.blocks`). A `type:` selector on the last parent names the parent row's blockType, so what that type's field accepts (wherever such a row sits) is the observed set, exactly as for a row nested in a document. Issue paths start at the normalised pattern (`layout[].blocks[0]`).
type Capabilities ¶
type Capabilities struct {
GraphQL GraphQLCapability `json:"graphql"`
Localization Localization `json:"localization"`
Reorder *bool `json:"reorder"`
OG *bool `json:"og"`
MethodOverride *bool `json:"method_override"`
Jobs Jobs `json:"jobs"`
Preferences Preferences `json:"preferences"`
CustomEndpoints []string `json:"custom_endpoints"`
}
Capabilities are the project-level facts.
type Census ¶ added in v0.3.0
type Census struct {
CensusVersion int `json:"census_version"`
Scope string `json:"scope"`
CLIVersion string `json:"cli_version"`
UpdatedAt time.Time `json:"updated_at"`
// Collections and Globals are keyed by slug. Entities are built and
// refreshed independently, so each carries its own built_at.
Collections map[string]*CensusEntity `json:"collections"`
Globals map[string]*CensusEntity `json:"globals"`
}
Census is the persisted artefact: one entry per entity that was read.
func DecodeCensus ¶ added in v0.3.0
DecodeCensus parses a persisted census, reporting false for anything this build cannot read — which the caller treats as a miss, never as an error.
func DecodeCensusErr ¶ added in v0.3.0
DecodeCensusErr is DecodeCensus with the reason, for the cache_unreadable warning a caller owes when a census that passed the header check still cannot be used (§8.3: a miss plus a warning, never a silent miss).
func (*Census) Entities ¶ added in v0.3.0
func (c *Census) Entities() []*CensusEntity
Entities returns every entity, collections first, each group sorted.
func (*Census) Entity ¶ added in v0.3.0
func (c *Census) Entity(kind, slug string) (*CensusEntity, bool)
Entity returns one entity's census.
func (*Census) Put ¶ added in v0.3.0
func (c *Census) Put(e *CensusEntity)
Put stores (or replaces) one entity's census.
func (*Census) WithEntity ¶ added in v0.3.0
func (c *Census) WithEntity(e *CensusEntity) *Census
WithEntity returns a shallow copy of the census in which e replaces the entity of its kind and slug. The receiver is not modified.
type CensusBuilder ¶ added in v0.3.0
type CensusBuilder struct {
// contains filtered or unexported fields
}
CensusBuilder accumulates one entity's census document by document.
func NewCensusBuilder ¶ added in v0.3.0
func NewCensusBuilder(kind, slug string) *CensusBuilder
NewCensusBuilder starts a census for one entity.
func (*CensusBuilder) AddDoc ¶ added in v0.3.0
func (b *CensusBuilder) AddDoc(id any, doc map[string]any)
AddDoc walks one document. id may be nil for a global.
func (*CensusBuilder) ApplyDepth1 ¶ added in v0.3.0
func (b *CensusBuilder) ApplyDepth1(doc map[string]any)
ApplyDepth1 walks a document read at depth 1 and marks every field whose depth-0 value was an id and whose depth-1 value is a whole document: that is a relationship, and an upload when the document is a file.
func (*CensusBuilder) Entity ¶ added in v0.3.0
func (b *CensusBuilder) Entity() *CensusEntity
Entity returns the entity under construction. Call Finish first.
func (*CensusBuilder) Finish ¶ added in v0.3.0
func (b *CensusBuilder) Finish() *CensusEntity
Finish finalises the entity: kinds, values, enum detection, references.
func (*CensusBuilder) ProbeCandidates ¶ added in v0.3.0
func (b *CensusBuilder) ProbeCandidates(n int) []any
ProbeCandidates returns up to n documents worth re-reading at depth 1 to learn which id-like values are relationship or upload ids. They are chosen greedily so that, between them, they cover as many distinct id-like fields as possible — every field at least once when n allows. Ranking documents by how many id-like values they hold picked the three logo-heavy pages and left the image, gallery and person fields of other block types unprobed.
type CensusChange ¶ added in v0.3.0
type CensusChange struct {
Change string `json:"change"` // added | removed | not_seen
What string `json:"what"` // block_type | field | path | path_type
Entity string `json:"entity"`
BlockType string `json:"block_type,omitempty"`
Path string `json:"path,omitempty"`
Message string `json:"message"`
}
CensusChange is one difference between two censuses of the same entity — what `pay blocks learn` reports so an agent sees "grid gained splitDesktop" instead of diffing two JSON dumps.
func DiffCensusEntity ¶ added in v0.3.0
func DiffCensusEntity(prev, next *CensusEntity) []CensusChange
DiffCensusEntity compares a previous and a new census of one entity. A nil previous census reports nothing: a first census has no history to diff.
Absence only means "no longer used" when the new read looked everywhere. A read that was truncated (a smaller --census-max-docs; a collection larger than the cap, whose newest-first window moved) or failed did not, so what it lacks is reported as change "not_seen", never "removed".
type CensusEntity ¶ added in v0.3.0
type CensusEntity struct {
Slug string `json:"slug"`
Kind string `json:"kind"`
BuiltAt time.Time `json:"built_at"`
// Draft reports that documents were read with draft=true, i.e. the census
// describes the newest draft of every document, which is what an editor
// is about to overwrite.
Draft bool `json:"draft"`
// Select is the top-level field projection the read used; empty means the
// whole document was read.
Select []string `json:"select"`
DocsScanned int `json:"docs_scanned"`
TotalDocs *int `json:"total_docs"`
// Truncated is true when the entity has more documents than were read.
Truncated bool `json:"truncated"`
// StoppedEarly is true when the read stopped after a full page with no
// block row at all: the entity does not use blocks.
StoppedEarly bool `json:"stopped_early,omitempty"`
MaxDocs int `json:"max_docs"`
Requests int `json:"requests"`
// RelationProbe says whether the depth-1 relationship probe ran: "ok",
// "none" (nothing to probe), "failed" or "skipped".
RelationProbe string `json:"relation_probe"`
// Error is set when the read failed; the entity then carries whatever was
// learned before the failure.
Error string `json:"error,omitempty"`
// FoldedDocs counts the distinct documents PayCLI's own successful writes
// folded into this entity after it was read (OverlayDocs): the census
// then also knows the shapes those writes created, without another read.
FoldedDocs int `json:"folded_docs,omitempty"`
// DocKeys are the ids of the documents the read counted ("_global" for a
// global) and FoldedKeys those of the documents folded in since. They are
// references, like Instances, never content; OverlayDocs uses them to
// count every document once however often it is written (§7.10c).
DocKeys []string `json:"doc_keys,omitempty"`
FoldedKeys []string `json:"folded_keys,omitempty"`
BlockRows int `json:"block_rows"`
Paths map[string]*CensusPath `json:"paths"`
Types map[string]*CensusType `json:"types"`
}
CensusEntity is one collection's or global's census.
func NewCensusEntity ¶ added in v0.3.0
func NewCensusEntity(kind, slug string) *CensusEntity
NewCensusEntity returns an empty entity census.
func OverlayDocs ¶ added in v0.3.0
func OverlayDocs(base *CensusEntity, persist bool, docs ...map[string]any) *CensusEntity
OverlayDocs returns a copy of base that also observes docs; base itself is never modified. It returns nil when base is nil: one document is not a census, and "never observed" against it would be a guess.
persist is true when the result will be written to the cache. Values seen once are then dropped exactly as a census read drops them (a value seen once is content, and content never reaches the disk), except for fields the census already treats as a vocabulary; and the folded documents are recorded in FoldedKeys so a later fold of the same document refreshes instead of counting it again.
func RunCensus ¶ added in v0.3.0
func RunCensus(ctx context.Context, opt CensusOptions, targets []CensusTarget) []*CensusEntity
RunCensus reads every target and returns one entity census per target, in target order. A failing entity is returned with Error set rather than failing the run: one unreadable collection must not hide what the others say.
func (*CensusEntity) PathPatterns ¶ added in v0.3.0
func (e *CensusEntity) PathPatterns() []string
PathPatterns lists the entity's blocks-field path patterns, shallowest first.
func (*CensusEntity) TypeSlugs ¶ added in v0.3.0
func (e *CensusEntity) TypeSlugs() []string
TypeSlugs lists the entity's observed blockTypes, sorted.
type CensusField ¶ added in v0.3.0
type CensusField struct {
Kind string `json:"kind"`
// KindSource is observed-census, or observed-depth-1 when the kind was
// settled by the relationship probe.
KindSource string `json:"kind_source"`
// Types counts JSON types: string, number, boolean, object, array, null.
Types map[string]int `json:"types"`
Present int `json:"present"`
NonNull int `json:"non_null"`
NonEmpty int `json:"non_empty"`
// Values are the most common scalar values with their counts. For a field
// that is not enum-like only REPEATED values are kept: a value seen once is
// content, not vocabulary.
Values []CensusValue `json:"values,omitempty"`
Distinct int `json:"distinct"`
DistinctCapped bool `json:"distinct_capped,omitempty"`
EnumLike bool `json:"enum_like"`
// Accepts counts blockTypes for a nested blocks field.
Accepts map[string]int `json:"accepts,omitempty"`
// Items is the total number of array / blocks rows observed.
Items int `json:"items,omitempty"`
// Fields are a group's keys or an array row's keys.
Fields map[string]*CensusField `json:"fields,omitempty"`
// Optional is set when a document the census already counted was folded
// in (OverlayDocs) holding this field empty, or holding it at all for the
// first time: the server keeps such a row, so the field is never reported
// likely_required, whatever the counts say.
Optional bool `json:"optional,omitempty"`
// contains filtered or unexported fields
}
CensusField is the observed profile of one key.
func (*CensusField) IsVocabulary ¶ added in v0.3.0
func (f *CensusField) IsVocabulary() bool
IsVocabulary reports whether the field behaves like a select: it was judged enum-like and every recorded value is an identifier. The second half re-checks a census written by a build whose rule accepted URLs, paths and numbers as vocabulary, so an old cache file cannot keep producing false value_not_in_enum warnings or `blocks new` defaults.
type CensusOptions ¶ added in v0.3.0
type CensusOptions struct {
Client *payload.Client
Now func() time.Time
MaxDocs int
// Concurrency bounds how many entities are read at once.
Concurrency int
// NoRelationProbe skips the depth-1 re-read of up to three documents per
// entity that turns an id-looking number into a relationship or upload.
NoRelationProbe bool
Logger *slog.Logger
}
CensusOptions configures one census run.
type CensusPath ¶ added in v0.3.0
type CensusPath struct {
Path string `json:"path"`
// Parent is the enclosing blocks pattern ("" at the top level).
Parent string `json:"parent"`
// ParentTypes counts the blockType of the row that owns this field, so a
// nested pattern knows it is `group.blocks` in 30 rows and `grid.blocks`
// in 12.
ParentTypes map[string]int `json:"parent_types,omitempty"`
// Field is the key inside the parent row (`blocks`), or the top-level
// field path for a top-level pattern.
Field string `json:"field"`
// Types counts every blockType seen at this pattern.
Types map[string]int `json:"types"`
Rows int `json:"rows"`
Docs int `json:"docs"`
// EmptyRows are `{}` entries — the orphan rows Payload's Postgres adapter
// leaves behind after a nested block restructure.
EmptyRows int `json:"empty_rows"`
// UntypedRows are non-empty rows with no blockType.
UntypedRows int `json:"untyped_rows"`
}
CensusPath is one blocks-field path PATTERN: `layout`, `layout[].blocks`, `layout[].blocks[].blocks`. Indices are erased because the question is "what may go at this position", not "what is at row 3".
type CensusRef ¶ added in v0.3.0
type CensusRef struct {
Kind string `json:"kind"`
Slug string `json:"slug"`
ID any `json:"id,omitempty"`
// Path is the concrete row path, e.g. layout[1].blocks[0].
Path string `json:"path"`
// Score is the number of non-empty fields on the row.
Score int `json:"score"`
}
CensusRef points at one real row. It is a reference, never a copy.
type CensusTarget ¶ added in v0.3.0
type CensusTarget struct {
Kind string
Slug string
// Draft asks for the newest draft of every document. It is set unless the
// entity is known NOT to have drafts; Payload ignores draft=true on an
// entity without versions, so an unknown answer costs nothing.
Draft bool
// Select narrows the read to the top-level fields that can hold blocks.
// Empty means "read everything" — the answer when no field list is known.
Select []string
}
CensusTarget is one entity the census should read.
func CensusTargets ¶ added in v0.3.0
func CensusTargets(m *Manifest, shards ShardLookup, only []string) []CensusTarget
CensusTargets decides which entities of a manifest can hold blocks and how to read each one cheaply.
A GraphQL-sourced shard is authoritative about field kinds, so an entity whose schema has no blocks field anywhere is skipped outright. Anything else — a REST-observed shard (introspection disabled), or no shard at all — is read, narrowed with `select` to the top-level fields that could contain a blocks array: arrays, groups, and fields whose kind the three-document sample could not settle (a `layout` that was [] in every sample).
only restricts the result to the named slugs (collections or globals).
type CensusType ¶ added in v0.3.0
type CensusType struct {
Slug string `json:"slug"`
Count int `json:"count"`
Docs int `json:"docs"`
// UsedIn counts rows per path pattern.
UsedIn map[string]int `json:"used_in"`
// Fields are the row's keys, excluding id and blockType.
Fields map[string]*CensusField `json:"fields"`
// Instances reference the most complete real rows, most complete first.
Instances []CensusRef `json:"instances"`
}
CensusType is everything observed about one blockType.
func MergeCensusTypes ¶ added in v0.3.0
func MergeCensusTypes(parts ...*CensusType) *CensusType
MergeCensusTypes combines observations of one blockType from several entities. With one input it returns that input unchanged.
func MergeCompatibleTypes ¶ added in v0.3.0
func MergeCompatibleTypes(parts ...*CensusType) *CensusType
MergeCompatibleTypes merges the parts that share the shape of the largest one and drops the rest.
type CensusValue ¶ added in v0.3.0
CensusValue is one observed scalar value.
type Collection ¶
type Collection struct {
Slug string `json:"slug"`
Labels Labels `json:"labels"`
GraphQL *GraphQLNames `json:"graphql"`
IDType string `json:"id_type"`
IDTypeSource string `json:"id_type_source"`
Reachability string `json:"reachability"`
Internal bool `json:"internal"`
Publishable bool `json:"publishable"`
PublishableReason *string `json:"publishable_reason"`
Flags Flags `json:"flags"`
FlagsSource FlagsSource `json:"flags_source"`
Permissions Permissions `json:"permissions"`
Stats Stats `json:"stats"`
FieldsCount int `json:"fields_count"`
FieldsSHA256 string `json:"fields_sha256"`
FieldsShard string `json:"fields_shard"`
FieldsSource string `json:"fields_source"`
TitleField *string `json:"title_field"`
DateFields []string `json:"date_fields"`
Warnings []string `json:"warnings"`
}
Collection is one index entry.
type Diagnostics ¶
type Diagnostics struct {
Requests int `json:"requests"`
ElapsedMS int64 `json:"elapsed_ms"`
Bytes int64 `json:"bytes"`
GraphQLBatches int `json:"graphql_batches"`
Degraded []string `json:"degraded"`
Warnings []string `json:"warnings"`
CreatedAndDeleted []string `json:"created_and_deleted"`
AuthCandidates []string `json:"auth_candidates"`
BlockSlugFiles []string `json:"block_slug_files"`
}
Diagnostics is the cost and degradation record for one discovery run.
type Discoverer ¶
type Discoverer struct {
// contains filtered or unexported fields
}
Discoverer runs §7's pipeline.
func New ¶
func New(opt Options) (*Discoverer, error)
New validates the options and returns a Discoverer.
func (*Discoverer) Run ¶
func (d *Discoverer) Run(ctx context.Context) (*Result, error)
Run executes §7's pipeline and returns the manifest plus one field shard per entity.
The one hard failure is §7.6's: discovery_failed (exit 10) is reserved for the case where REST-only discovery also fails, i.e. GET {api_path}/access itself did not return parseable JSON. A working project must never be made unusable by a GraphQL-layer refusal.
type Field ¶
type Field struct {
Name string `json:"name"`
Path string `json:"path"`
GraphQLPath string `json:"graphql_path"`
Parent *string `json:"parent"`
PayloadType string `json:"payload_type"`
PayloadTypeConfidence string `json:"payload_type_confidence"`
GraphQLType *string `json:"graphql_type"`
JSONType string `json:"json_type"`
Required *bool `json:"required"`
RequiredSource string `json:"required_source"`
HasMany bool `json:"has_many"`
Localized *bool `json:"localized"`
LocalizedSource string `json:"localized_source"`
ReadOnly bool `json:"read_only"`
Options []string `json:"options"`
OptionsSource string `json:"options_source"`
RelationTo []string `json:"relation_to"`
RelationToSource string `json:"relation_to_source"`
Polymorphic bool `json:"polymorphic"`
// WriteShape is mandatory and non-null on every relationship, upload and
// polymorphic field and null on every other kind. §9.10's --set coercion
// reads exactly this key.
WriteShape *string `json:"write_shape"`
Queryable bool `json:"queryable"`
Operators []string `json:"operators"`
Sortable bool `json:"sortable"`
SortableConfidence string `json:"sortable_confidence"`
HookMutated string `json:"hook_mutated"`
Label string `json:"label"`
}
Field is one entry of a field shard.
**Every key below is mandatory on every entry**, using null (or the documented sentinels "n/a" / "unknown") where it does not apply. An agent parses a field entry without presence checks, so `jq '.fields[] | select(.required)'` is total. A unit test asserts the exact key set.
type FieldDoc ¶ added in v0.2.0
type FieldDoc struct {
Description string `json:"description"`
Key string `json:"key"`
// Source is "project-source"; it exists so that a single entry is
// self-describing when it is lifted out of the map.
Source string `json:"source"`
}
FieldDoc is one field's human documentation as the project wrote it.
Key is always reported with Description: `admin.description` is Payload's own field-level key, but PayCLI also accepts a `custom.*` neighbour, and an agent is entitled to know which one a sentence came from.
type Fingerprint ¶
type Fingerprint struct {
TopologySHA256 string `json:"topology_sha256"`
SchemaSHA256 string `json:"schema_sha256"`
CheckedAt time.Time `json:"checked_at"`
ServerIdentity string `json:"server_identity"`
}
Fingerprint carries §8.4's two invalidation levels. Level 1 is identity-scoped and cheap; Level 2 is identity-independent and is the only permission-free schema-version proxy the API offers.
type Flags ¶
type Flags struct {
Upload *bool `json:"upload"`
Auth *bool `json:"auth"`
UseAPIKey *bool `json:"use_api_key"`
Versions *bool `json:"versions"`
Drafts *bool `json:"drafts"`
Trash *bool `json:"trash"`
Folders *bool `json:"folders"`
Duplicate *bool `json:"duplicate"`
Orderable *bool `json:"orderable"`
EndpointsDisabled *bool `json:"endpoints_disabled"`
}
Flags are §7.6's tri-state capability flags. nil means "never learned" and must never block an operation locally — the operation is attempted and the server's answer is classified after the fact.
type FlagsSource ¶
type FlagsSource struct {
Upload string `json:"upload"`
Auth string `json:"auth"`
UseAPIKey string `json:"use_api_key"`
Versions string `json:"versions"`
Drafts string `json:"drafts"`
Trash string `json:"trash"`
Folders string `json:"folders"`
Duplicate string `json:"duplicate"`
Orderable string `json:"orderable"`
EndpointsDisabled string `json:"endpoints_disabled"`
}
FlagsSource carries one provenance string per flag.
type Global ¶
type Global struct {
Slug string `json:"slug"`
Labels Labels `json:"labels"`
GraphQL *GraphQLNames `json:"graphql"`
Reachability string `json:"reachability"`
Internal bool `json:"internal"`
Flags GlobalFlags `json:"flags"`
FlagsSource GlobalFlagsSource `json:"flags_source"`
Permissions GlobalPermissions `json:"permissions"`
FieldsCount int `json:"fields_count"`
FieldsSHA256 string `json:"fields_sha256"`
FieldsShard string `json:"fields_shard"`
FieldsSource string `json:"fields_source"`
Warnings []string `json:"warnings"`
}
Global is one global index entry. A global has no id, no bulk verbs and no trash, so the collection keys that cannot apply are absent rather than written as a misleading false.
type GlobalFlags ¶
GlobalFlags is the reduced flag set a global can have.
type GlobalFlagsSource ¶
GlobalFlagsSource carries provenance for GlobalFlags.
type GlobalPermissions ¶
type GlobalPermissions struct {
Read bool `json:"read"`
Update bool `json:"update"`
ReadVersions bool `json:"read_versions"`
FieldLevel *bool `json:"field_level"`
}
GlobalPermissions is the /api/access globals entry.
type GraphQLCapability ¶
type GraphQLCapability struct {
Mode string `json:"mode"`
Introspection *bool `json:"introspection"`
Path string `json:"path"`
Detail string `json:"detail,omitempty"`
Hint string `json:"hint,omitempty"`
}
GraphQLCapability is §7.6's ladder result. Introspection is tri-state: on a route_missing or unreachable endpoint PayCLI learned nothing about it.
type GraphQLNames ¶
type GraphQLNames struct {
Singular string `json:"singular"`
Plural string `json:"plural,omitempty"`
Count string `json:"count,omitempty"`
Source string `json:"source"`
}
GraphQLNames is the slug <-> GraphQL type mapping recovered by §7.3's index-for-index zip of the Access type against Query.docAccess*. It is nil when no GraphQL name could be established, and the entity then runs REST-only.
type IDLadderInput ¶
type IDLadderInput struct {
// GraphQL is the Stage-1 answer, "" when GraphQL was unavailable.
GraphQL string
// SampleID is docs[0].id from GET /{slug}?limit=1&depth=0&select[id]=true.
SampleID any
// SampleIDPresent distinguishes "the collection is empty" from "id was
// null".
SampleIDPresent bool
// VersionParentID is docs[0].parent from /{slug}/versions, which is the
// *document* id while the version's own id is a different row.
VersionParentID any
VersionParentPresent bool
// Configured is the profile's id_type pin.
Configured string
}
IDLadderInput is §7.6's REST id_type ladder, expressed as already-fetched samples so the ladder itself is pure and table-testable.
type Identity ¶
type Identity struct {
AuthMode string `json:"auth_mode"`
AuthCollection *string `json:"auth_collection"`
AuthCollectionSource string `json:"auth_collection_source"`
UserID any `json:"user_id"`
Verified bool `json:"verified"`
CanAccessAdmin bool `json:"can_access_admin"`
Strategy string `json:"strategy"`
KeyFingerprint string `json:"key_fingerprint"`
}
Identity is Stage 0's verified answer to "who am I". The /me response body itself is never written to disk (§7.8.3) — it carries the API key in plaintext on a project with useAPIKey.
type IntroEnumValue ¶
type IntroEnumValue struct {
Name string `json:"name"`
}
IntroEnumValue is one ENUM value.
type IntroField ¶
type IntroField struct {
Name string `json:"name"`
Args []IntroArg `json:"args"`
Type *TypeRef `json:"type"`
}
IntroField is one field of an OBJECT type.
func (*IntroField) Arg ¶
func (f *IntroField) Arg(name string) (IntroArg, bool)
Arg returns a named argument.
func (*IntroField) HasArg ¶
func (f *IntroField) HasArg(name string) bool
HasArg reports whether a named argument exists.
type IntroInputField ¶
IntroInputField is one field of an INPUT_OBJECT type.
type IntroType ¶
type IntroType struct {
Kind string `json:"kind"`
Name string `json:"name"`
Fields []IntroField `json:"fields"`
InputFields []IntroInputField `json:"inputFields"`
EnumValues []IntroEnumValue `json:"enumValues"`
PossibleTypes []IntroTypeName `json:"possibleTypes"`
}
IntroType is one __type(name:) result.
func (*IntroType) FieldNames ¶
FieldNames returns the OBJECT field names.
func (*IntroType) InputFieldNames ¶
InputFieldNames returns the INPUT_OBJECT field names.
func (*IntroType) PossibleTypeNames ¶
PossibleTypeNames returns a UNION's member names, preserving schema order — the *_RelationTo enum and its matching union list their members in the same order, and the polymorphic target mapping depends on that.
type IntroTypeName ¶
IntroTypeName is a bare type reference in possibleTypes.
type Jobs ¶
type Jobs struct {
Collection *string `json:"collection"`
StatsGlobal *string `json:"stats_global"`
}
Jobs and Preferences name Payload's internal collections when this project has them, so a command never has to hardcode the slug.
type Kind ¶
type Kind struct {
PayloadType string
Confidence string
JSONType string
HasMany bool
ReadOnly bool
Polymorphic bool
Options []string
OptionsSource string
RelationTo []string
RelationToSource string
// Blocks is the UNION member list for a blocks field. These are
// interfaceNames, NOT blockType slugs — §7.10 resolves the real slugs from
// project source.
BlockInterfaces []string
// Children is the named type whose fields should be walked as nested
// group/array entries, or "" when there are none.
Children string
// Leaves are type names this inference needs resolved to be complete.
Leaves []string
}
Kind is the result of §7.4's field-kind inference table.
func InferKind ¶
InferKind implements §7.4's table. It is pure: everything it needs is either in the TypeRef or already in the Schema, and anything missing is reported through Leaves so the caller can batch one more __type round.
ownerSingular is the entity's GraphQL type name, used only to recognise the {Owner}_{field}* naming convention.
type Labels ¶
type Labels struct {
Singular string `json:"singular"`
Plural string `json:"plural"`
Source string `json:"source"`
}
Labels are §7.5 probe 8's harvest. They are never blank, never null and never half-parsed: a failed parse falls back to the title-cased slug with source "derived".
func DeriveLabels ¶
DeriveLabels is the fallback: the title-cased slug with - and _ turned into spaces (crm-contacts -> "Crm Contacts"). Labels are never blank, never null and never half-parsed.
singularType is the entity's GraphQL type name when one is known (CrmContact); it yields a genuinely singular label without any pluralisation guessing, which §7.3 forbids. Without it the plural form is reused, because inventing a singular by stripping an "s" mangles real slugs (`cms` -> `Cm`).
func LabelsFromMessage ¶
LabelsFromMessage builds the labels for a successful probe-8 parse. The singular still comes from the GraphQL type name or the slug, because the message only ever carries the plural.
type LearnedFailure ¶
type LearnedFailure struct {
Operator string `json:"operator"`
Collection string `json:"collection"`
Evidence string `json:"evidence"`
LearnedAt time.Time `json:"learned_at"`
}
LearnedFailure is §7.11's reactive record: an operator that provably failed on a collection, so the next use warns before sending.
type Limitation ¶
type Limitation struct {
Code string `json:"code"`
Entity string `json:"entity,omitempty"`
Field string `json:"field,omitempty"`
Detail string `json:"detail,omitempty"`
Mitigation string `json:"mitigation"`
}
Limitation is a declared hole. Holes are declared, never guessed.
type LocaleAnalysis ¶
type LocaleAnalysis struct {
// Candidates are the true locale codes, or nil when the sample proved
// nothing. These are real codes, unlike the formatName()-mangled names the
// GraphQL LocaleInputType enum exposes.
Candidates []string
// Localized are the field names that came back as a locale map.
Localized []string
// NotLocalized are the field names that did not, on a document where at
// least one sibling did — which is the only evidence REST offers for a
// per-field false.
NotLocalized []string
}
LocaleAnalysis is the result of reading a `?locale=all` sample.
func AnalyzeLocaleAll ¶
func AnalyzeLocaleAll(docs []map[string]any) LocaleAnalysis
AnalyzeLocaleAll implements §7.9b step 2. A localized field comes back as {"en": …, "de": …} and the keys are true codes. The candidate set is the intersection of the key sets that appeared in at least two fields across the sampled documents — one field alone could be an ordinary group whose keys happen to look like codes.
type LocaleInput ¶
type LocaleInput struct {
// Configured is the profile's locales pin.
Configured []string
// Analysis is the locale=all sample result.
Analysis LocaleAnalysis
// EnumNames are LocaleInputType's enumValues, which are
// formatName()-mangled and are therefore NEVER usable as codes.
EnumNames []string
// GraphQLReachable reports whether the cross-validation could run at all.
GraphQLReachable bool
}
LocaleInput is §7.9b's ladder input, already fetched.
type Localization ¶
type Localization struct {
Enabled *bool `json:"enabled"`
// EnabledSource is the story behind `enabled`, which §7.8.3 requires of
// every derived fact: "graphql-arg" / "graphql-arg-absent" when the plural
// Query field settled it, "locale-all" when a ?locale=all sample proved it
// positively, "configured" when the profile pinned locale codes, and
// "unknown" while it is still nil.
EnabledSource string `json:"enabled_source"`
Locales []string `json:"locales"`
// Default is Payload's localization.defaultLocale. No API exposes it, so
// it is null unless something actually established it — it is NEVER the
// first entry of a sorted locale list (§21.3 rule 1: a defaulted value is
// not a discovered fact).
Default *string `json:"default"`
DefaultSource string `json:"default_source"`
LocalesSource string `json:"locales_source"`
FallbackDefault *string `json:"fallback_default"`
LocaleCount *int `json:"locale_count"`
}
Localization models §7.9. `enabled` is tri-state because REST cannot tell a non-localised project from a localised one — `locale` is accepted and ignored when localisation is off.
type Manifest ¶
type Manifest struct {
ManifestVersion int `json:"manifest_version"`
Generation string `json:"generation"`
CLIVersion string `json:"cli_version"`
GeneratedAt time.Time `json:"generated_at"`
ExpiresAt time.Time `json:"expires_at"`
Meta ManifestMeta `json:"meta"`
Fingerprint Fingerprint `json:"fingerprint"`
Source Source `json:"source"`
Identity Identity `json:"identity"`
Capabilities Capabilities `json:"capabilities"`
Collections []*Collection `json:"collections"`
Globals []*Global `json:"globals"`
Unreachable []Unreachable `json:"unreachable"`
Limitations []Limitation `json:"limitations"`
UnsupportedOperators []UnsupportedOperator `json:"unsupported_operators"`
LearnedOperatorFailures []LearnedFailure `json:"learned_operator_failures"`
Diagnostics Diagnostics `json:"diagnostics"`
}
Manifest is the discovery index — everything help text, `pay collections`, `pay explain --slim`, shell completion and target resolution need. It stays roughly 1 KB per collection regardless of field count, because the fields themselves live in per-entity Shards.
func DecodeManifest ¶
DecodeManifest parses a manifest index. A manifest_version other than the current one is reported as a miss by returning ok == false rather than an error, because §8.3 requires every cache read failure to be a miss.
func NewManifest ¶
func NewManifest() *Manifest
NewManifest returns a manifest with every slice non-nil, so the JSON form never has a null where an agent expects an array.
func (*Manifest) AddLimitation ¶
AddLimitation appends a limitation with its §7.6 mitigation, de-duplicating on (code, entity, field) so a per-collection hole is recorded once.
func (*Manifest) AddUnreachable ¶
AddUnreachable appends an unreachable entry, de-duplicating on slug+kind.
func (*Manifest) Collection ¶
func (m *Manifest) Collection(slug string) (*Collection, bool)
Collection returns the index entry for a slug.
func (*Manifest) CollectionSlugs ¶
CollectionSlugs returns every collection slug in index order (key-sorted).
func (*Manifest) GlobalSlugs ¶
GlobalSlugs returns every global slug in index order.
func (*Manifest) LearnedOperatorFailure ¶
func (m *Manifest) LearnedOperatorFailure(operator, collection string) (LearnedFailure, bool)
LearnedOperatorFailure looks the memo up for one operator on one collection. It is deliberately NOT project-wide: `all` failing on crm-contacts says nothing about `all` on pages, and warning about the second because of the first would be the same "assume, do not learn" mistake §1 conflict 36 exists to correct.
func (*Manifest) RecordOperatorFailure ¶
RecordOperatorFailure appends §7.11's reactive memo, or refreshes the record that is already there. It reports whether the manifest changed, so a caller can skip an expensive cache rewrite when it did not.
Records are keyed by (operator, collection): re-running the same failing query updates the evidence and the timestamp instead of growing the file without bound.
func (*Manifest) ResolveCollection ¶
func (m *Manifest) ResolveCollection(slug string) (*Collection, error)
ResolveCollection looks a slug up and produces §11's collection_unknown with did-you-mean candidates when it is absent.
func (*Manifest) ResolveGlobal ¶
ResolveGlobal is ResolveCollection for globals.
func (*Manifest) Revision ¶
Revision is a short, stable identifier for the manifest's content, used as the discovery revision stamped into generated skill docs (§14) and into meta.discovery_revision.
func (*Manifest) Sort ¶
func (m *Manifest) Sort()
Sort orders the index deterministically: collections and globals by slug, and every declared hole by (code, entity, field). Two discovery runs against an unchanged server must produce byte-identical manifests apart from the timestamps, or the §8.4 fingerprints would flap.
type ManifestMeta ¶
type ManifestMeta struct {
Scope string `json:"scope"`
Profiles []string `json:"profiles"`
BaseURL string `json:"base_url"`
APIPath string `json:"api_path"`
GraphQLPath string `json:"graphql_path"`
HeaderNames []string `json:"header_names"`
KeyFingerprint string `json:"key_fingerprint"`
CreatedAt time.Time `json:"created_at"`
ConfirmedAt time.Time `json:"confirmed_at"`
}
ManifestMeta binds the manifest to the cache scope that produced it (§8.1). It carries the sorted header *names* only — a [profiles.X.headers] value is never written to any artefact (§7.8.3).
type Options ¶
type Options struct {
Client *payload.Client
BaseURL string
APIPath string
APIPathSource string
// GraphQLRoute is routes.graphQL, defaulting to /graphql. graphql_path is
// DERIVED from api_path + this (§1 conflict 34), never an independent
// literal.
GraphQLRoute string
GraphQLPath string
GraphQLPathSource string
AuthMode string
AuthCollection string
// AuthCollectionSource is "configured" when the slug was pinned.
AuthCollectionSource string
// CachedAuthCollection is a slug already resolved for this
// (normURL, keyFP) pair by a previous run (§7.0e). When set, Stage -1 is
// skipped entirely.
CachedAuthCollection string
// CredentialAbsent reports anonymous operation. Stage 0's /me request is
// then not made at all and discovery continues.
CredentialAbsent bool
KeyFingerprint string
Concurrency int
Profile string
Profiles []string
ScopeKey string
HeaderNames []string
CLIVersion string
Generation string
Now func() time.Time
TTL time.Duration
// Deep widens Stage 3 to every collection and adds the document census.
Deep bool
// AllowWriteProbes enables §7.7's empty-POST harvest. It creates a real
// document in any collection with no required fields, so it is off by
// default and the flag name is deliberately unpleasant.
AllowWriteProbes bool
// NoLabels skips §7.5 probe 8, which is the only DELETE verb discovery
// ever issues.
NoLabels bool
// NoProbes disables Stage 3 entirely.
NoProbes bool
ConfiguredIDType string
ConfiguredLocales []string
ConfiguredBlocks map[string][]string
CustomEndpoints []string
PayloadVersion string
PayloadVersionSource string
DBAdapter string
DBAdapterSource string
// ProjectBlockSlugs are §7.10's `slug:` literals found on the local
// filesystem, and ProjectBlockSlugFiles the absolute files they came from.
ProjectBlockSlugs []string
ProjectBlockSlugFiles []string
// ProjectBlockInterfaces maps an `interfaceName:` literal found on the
// local filesystem to the `slug:` declared beside it in the same object
// (CallToActionBlock -> cta). It is the other half of §7.10: a blocks
// field's GraphQL union publishes interfaceNames, and only this map turns
// one into a blockType the REST API will accept. Empty is survivable —
// every union member then resolves through the interfaceName heuristic and
// is labelled as inferred — but it is never guessed at silently.
ProjectBlockInterfaces map[string]string
// ProjectBlockDecls are the block declarations read off disk, keyed by
// slug: each block's own `fields:` entries and whether the whole array was
// parseable. This is the ONLY source of a block field's required-ness —
// Payload generates no input type for a block type, so §7.4's NON_NULL
// trick cannot reach inside one. Absent for every plugin-provided block,
// which is why the answer stays tri-state.
ProjectBlockDecls map[string]BlockSourceDecl
// ProjectFieldDocs are the per-field `admin: { description: '…' }`
// instructions read from the project's own collection and global configs,
// keyed by entity slug and then by field name, with ProjectFieldDocFiles
// naming the file each entity's came from.
//
// Payload publishes admin.description nowhere in the API — it is admin-UI
// metadata, absent from both REST responses and GraphQL introspection — so
// the file on disk is the only source there is, and an entity whose config
// PayCLI cannot see (a plugin's collection lives in node_modules) has none.
ProjectFieldDocs map[string]map[string]FieldDoc
ProjectFieldDocFiles map[string]string
// ProjectAuthSlugs are auth-collection slug literals from the project's
// own payload.config.ts, used only to widen Stage -1's candidate set.
ProjectAuthSlugs []string
// ProjectRoutes are `routes:` literals, named in the endpoint_not_payload
// hint when nothing accepted.
ProjectRoutes []string
Logger *slog.Logger
}
Options configures one discovery run.
Everything here is a plain value rather than a config.* type on purpose: internal/discovery must not depend on the configuration layer's shape, and every project fact it needs (block slugs, the payload version, the db adapter) is produced by a local filesystem scan the caller already ran.
type Permissions ¶
type Permissions struct {
Create bool `json:"create"`
Read bool `json:"read"`
Update bool `json:"update"`
Delete bool `json:"delete"`
ReadVersions bool `json:"read_versions"`
Unlock bool `json:"unlock"`
FieldLevel *bool `json:"field_level"`
}
Permissions is the /api/access entry for this identity. field_level is tri-state: `fields` is the boolean true for a privileged key and an object for a restricted one, and it is NEVER used as a field-name source (§1 conflict 12).
type Preferences ¶
type Preferences struct {
Collection *string `json:"collection"`
}
Preferences names the preferences collection when present.
type Result ¶
type Result struct {
Manifest *Manifest
// Shards are keyed by the cache-relative shard path, e.g.
// "fields/pages.json", so a caller can hand them straight to cache.Set.
Shards map[string]*Shard
AuthCollection string
AuthCollectionSource string
// AuthResolved reports that Stage -1 ran and produced a slug worth
// persisting to auth-resolution.json.
AuthResolved bool
APIPath string
APIPathSource string
GraphQLPath string
GraphQLPathSource string
Warnings []output.Warning
}
Result is one discovery run's output. Nothing here has been written to disk: persistence is internal/cache's job, and keeping it there means a failed write is a warning rather than a corrupt manifest.
type Schema ¶
type Schema struct {
// Types are the resolved __type results, keyed by type name.
Types map[string]*IntroType
// SlugBySingular maps a GraphQL singular type name back to its slug, so a
// monomorphic relationship target is reported as a slug, never as a type
// name an agent cannot pass on the command line.
SlugBySingular map[string]string
}
Schema is everything the field-kind inference needs to resolve a named type without another round trip.
type Shard ¶
type Shard struct {
Generation string `json:"generation"`
Slug string `json:"slug"`
SHA256 string `json:"sha256"`
Fields []Field `json:"fields"`
// JoinFields lists read-only join fields, which must be requested with
// joins[field][limit] rather than selected like a normal field.
JoinFields []string `json:"join_fields"`
// Blocks maps a blocks field's path to its resolved blockType slugs. It is
// null — not an empty map — when no source could resolve them, which is
// the difference between "this project has no blocks" and "PayCLI does not
// know" (§7.10). Every entry is resolved from THAT FIELD's own GraphQL
// union, so two blocks fields on the same entity can and do carry
// different lists.
Blocks map[string][]string `json:"blocks"`
// BlocksSource is the entity-wide summary: the one source when every
// blocks field resolved the same way, "mixed" when they did not, "unknown"
// when the entity has no resolved blocks field at all. It is a SUMMARY —
// BlockFields[path].Source is the per-field answer and the one an agent
// should read.
BlocksSource string `json:"blocks_source"`
// BlockFields carries the full per-field answer for every blocks field,
// including each slug's own provenance and the GraphQL union the slugs
// were derived from. It is null when the entity has no blocks field.
BlockFields map[string]BlockField `json:"block_fields"`
// BlockSchemas is every reachable block type's OWN field schema, keyed by
// blockType slug — what is inside a cta, not merely that cta exists. It is
// null on a shard written before the key existed and on a REST-only shard
// (there is no union to read), which is why every consumer treats null as
// "not discovered" and says so rather than as "this block has no fields".
BlockSchemas map[string]BlockTypeSchema `json:"block_schemas"`
// RequiredPaths is the flattened list of paths whose required is true.
RequiredPaths []string `json:"required_paths"`
// FieldDocs are the per-field human instructions this entity's own config
// declares — `admin: { description: '…' }` — keyed by field path.
//
// It is a SEPARATE map rather than three more keys on every field entry,
// and that is a size decision with a measured reason: `pay describe pages`
// carries ~90 field entries, so three keys each would add ~16 KB to the
// most frequently run command in order to publish null 90 times. Here the
// cost is proportional to what the project actually documented, and is
// nothing at all on a project that documented nothing.
//
// FieldDocsSource is what distinguishes "scanned, found none" from "never
// scanned": a producer records no map rather than an empty one.
FieldDocs map[string]FieldDoc `json:"field_docs,omitempty"`
// FieldDocsSource is "project-source" when this entity's config was read
// off disk and "unknown" when it was not — the normal case for a
// plugin-provided collection, whose config lives in node_modules.
FieldDocsSource string `json:"field_docs_source,omitempty"`
// FieldDocsFile is the absolute path the docs were read from, "" when
// there is none.
FieldDocsFile string `json:"field_docs_file,omitempty"`
}
Shard is one entity's field schema — the artefact decoded only for the collection named on the command line (§8.2).
func (*Shard) BlockFieldFor ¶ added in v0.2.0
func (s *Shard) BlockFieldFor(path string) (BlockField, bool)
BlockFieldFor returns the full per-field record for a blocks field.
func (*Shard) BlockSchemaFor ¶ added in v0.2.0
func (s *Shard) BlockSchemaFor(slug string) (BlockTypeSchema, bool)
BlockSchemaFor returns one blockType's field schema and whether the shard carries it. The bool is the tri-state: false is "PayCLI did not discover this block's interior", never "the block has no fields".
func (*Shard) BlockSlugsFor ¶ added in v0.2.0
BlockSlugsFor returns every blockType slug reachable from any blocks field of this entity, sorted and deduplicated.
func (*Shard) BlockTypesFor ¶ added in v0.2.0
BlockTypesFor returns the blockType slugs one field accepts, and whether PayCLI knows. The bool is the §7.10 tri-state: false means unknown, not "accepts nothing".
func (*Shard) DateFields ¶
DateFields returns every field whose payload_type is date, in shard order.
func (*Shard) DocumentedPaths ¶ added in v0.2.0
DocumentedPaths lists every field path carrying a description, sorted.
func (*Shard) FieldDocFor ¶ added in v0.2.0
FieldDocFor returns one field path's documentation and whether the project declared any. The bool is the tri-state: false is "nobody wrote one", never an empty sentence.
func (*Shard) Finalize ¶
func (s *Shard) Finalize()
Finalize sorts the derived lists, recomputes required_paths and stamps the content hash. It is the single place a shard becomes publishable, so a caller cannot forget the hash the index refers to.
func (*Shard) QueryablePaths ¶
QueryablePaths returns the subset that Payload's where parser accepts. It feeds query.Schema, whose nil-means-unknown contract this preserves: an empty shard yields nil, never an empty non-nil slice that would reject everything.
func (*Shard) SetBlockField ¶ added in v0.2.0
func (s *Shard) SetBlockField(bf BlockField)
SetBlockField records ONE blocks field's resolution and keeps every derived view of it consistent: block_fields[path] (the full answer), blocks[path] (the slug list §9.7 validates a --set blockType against) and blocks_source (the entity-wide summary).
It is the only writer of those three keys, so they cannot drift apart and a producer cannot record slugs without also recording where they came from (§7.8.3).
func (*Shard) SetFieldDocs ¶ added in v0.2.0
SetFieldDocs records this entity's per-field documentation and the file it was read from, keeping the three keys that describe it consistent.
It is the only writer of them, so a producer cannot record documentation without also recording where it came from (§7.8.3). Calling it with an empty map still records the SOURCE: "the config was read and documents nothing" is a different, and more useful, answer than "no config was read".
func (*Shard) SortablePaths ¶
SortablePaths returns the sortable subset, with the same nil contract.
type ShardLookup ¶ added in v0.3.0
ShardLookup returns an entity's field shard, or nil.
type Source ¶
type Source struct {
BaseURL string `json:"base_url"`
APIPath string `json:"api_path"`
APIPathSource string `json:"api_path_source"`
GraphQLPath string `json:"graphql_path"`
GraphQLPathSource string `json:"graphql_path_source"`
PoweredBy string `json:"powered_by"`
PayloadVersion *string `json:"payload_version"`
PayloadVersionSource string `json:"payload_version_source"`
DBAdapter string `json:"db_adapter"`
DBAdapterSource string `json:"db_adapter_source"`
}
Source records how PayCLI reached the server and what it could learn about the project itself. payload_version and db_adapter are not obtainable from the API at all (§7.11), so both are nullable with provenance.
type Stats ¶
Stats is the optional document census. total_docs is nil unless the caller asked for it (--deep): one count request per collection is 49 requests on the live project and §7.1's cold budget is four.
type TopologyProjection ¶
type TopologyProjection struct {
Collections map[string][]string `json:"c"`
Globals map[string][]string `json:"g"`
Admin bool `json:"admin"`
}
TopologyProjection is §8.4's Level-1 normalised projection of GET /api/access. Only key *sets* are hashed, never values, so a permission flip still changes the fingerprint (a collapsed entry has a different key set) while transient data does not.
func ProjectTopology ¶
func ProjectTopology(collections, globals map[string]any, canAccessAdmin bool) TopologyProjection
ProjectTopology builds the projection from a decoded /api/access body.
type TypeRef ¶
type TypeRef struct {
Kind string `json:"kind"`
Name string `json:"name"`
OfType *TypeRef `json:"ofType"`
}
TypeRef is a GraphQL type reference as introspection returns it: a chain of NON_NULL / LIST wrappers around one named type.
type Unreachable ¶
type Unreachable struct {
Slug string `json:"slug"`
Kind string `json:"kind"`
Reason string `json:"reason"`
Detail string `json:"detail"`
}
Unreachable records an entity PayCLI knows about but cannot fully use, with the evidence for why.
type UnsupportedOperator ¶
type UnsupportedOperator struct {
Operator string `json:"operator"`
Collection string `json:"collection,omitempty"`
Reason string `json:"reason"`
}
UnsupportedOperator is a pre-emptive client-side block. It is only ever populated when db_adapter_source == "configured" (§7.11): on MongoDB `all` maps to $all and works, so a guessed list would make PayCLI refuse a query the server would have answered.