Documentation
¶
Index ¶
- Variables
- func ColumnGoName(s string) string
- func ConnectPackage(goPackageName string) string
- func ConnectProcedureConst(protoService, protoMethod string) string
- func ConnectServiceGoName(protoService string) string
- func ConnectServiceNameConst(protoService string) string
- func EntityFieldName(entityName string) string
- func EntityListFieldName(entityName string) string
- func EsFieldName(protoFieldName string) string
- func EsMethodName(protoMethodName string) string
- func FrontendServiceName(project, frontend string) string
- func GoCamelCase(s string) string
- func GoFieldName(protoFieldName string, siblings []string) string
- func GoFieldNames(protoFieldNames []string) []string
- func GoPackage(name string) string
- func GoTypeName(protoPkg, fullName string) string
- func Humanize(s string) string
- func HumanizeLower(s string) string
- func KCLIdentifier(name string) string
- func Pluralize(s string) string
- func ProtoCamelCase(snakeCase string) string
- func ProtoPackageBase(protoPackage string) string
- func ProtoPackageVersion(protoPackage string) string
- func ServiceHookFile(name string) string
- func ServiceHookFileLegacy(name string) string
- func ServicePackage(name string) string
- func ToExportedFieldName(pkg string) string
- func ToKebabCase(s string) string
- func ToPascalCase(s string) string
- func ToSnakeCase(s string) string
- func ToSnakeCaseWords(s string) string
- func TripsReviveVarNaming(goIdent string) bool
Constants ¶
This section is empty.
Variables ¶
var CompoundWords = []string{
"GitHub", "GitLab", "OAuth", "OpenAI", "OpenAPI", "OpenID", "OpenBao",
"GraphQL", "PostgreSQL", "MySQL", "WebSocket", "WebAuthn",
"JavaScript", "TypeScript", "YouTube", "LinkedIn", "PayPal",
}
CompoundWords are words conventionally written with an inner capital. Each is matched case-sensitively, as a whole word.
var GoInitialisms = []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DB", "DNS", "EOF", "GUID",
"HTML", "HTTP", "HTTPS", "ID", "IO", "IP", "JSON", "JWT", "LHS",
"LLM", "MCP", "QPS",
"RAM", "RHS", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP",
"TLS", "TTL", "UDP", "UI", "UID", "UUID", "URI", "URL",
"UTF8", "VM", "XML", "XMPP", "XSRF", "XSS",
}
GoInitialisms are common Go initialisms that should be all-caps. This is the single source of truth — all packages must import from here.
var GoInitialismsMap = func() map[string]bool { m := make(map[string]bool, len(GoInitialisms)) for _, v := range GoInitialisms { m[strings.ToLower(v)] = true } return m }()
GoInitialismsMap provides O(1) lookup for initialism detection (lowercase keys).
var SnakeInitialisms = []string{
"NATS", "OIDC", "SAML", "SSO", "MFA", "TOTP", "AWS", "GCP", "GCS", "GKE",
"CLI", "SDK", "ORM", "CRD", "KV", "PR", "CI", "CD", "IDP", "JWKS",
}
SnakeInitialisms are initialisms the word split knows beyond GoInitialisms. They are kept apart because GoInitialisms also drives ToPascalCase's capitalisation of generated Go identifiers, which these must not change.
Functions ¶
func ColumnGoName ¶ added in v0.1.45
ColumnGoName is the Go field name forge's generated ORM gives a column on a db.<Entity> struct: title-case each '_'/'-'-separated segment, then spell Go initialisms in capitals the way gofmt-era Go code does ("id" → "ID", "org_id" → "OrgID", "api_key" → "APIKey", "address_line_2" → "AddressLine2", "base64item" → "Base64item", "user_ids" → "UserIds" — a plural is not an initialism, as in staticcheck ST1003).
This is FORGE's rule for FORGE's types, and it is deliberately not protoc-gen-go's: the generated pb field over the same name can differ ("id" → Id, "base64item" → Base64Item, "address_line_2" → AddressLine_2). A conversion between the two structs must spell each side with its own rule — GoFieldName for the pb message, this for the db struct. It once claimed to match protoc-gen-go, and every pb field reference built from it broke on the first name with a digit in it.
It used to apply no initialisms ("Id", "UserId"), for no reason but that changing it renames ORM struct fields in every existing project; the store is Go, and Go spells these ID and UserID (reliant P5b, F12.6).
func ConnectPackage ¶ added in v0.1.45
ConnectPackage is the Go package protoc-gen-connect-go writes a file's services into: the file's Go package name plus the default package_suffix "connect" ("billingv1" → "billingv1connect").
func ConnectProcedureConst ¶ added in v0.1.45
ConnectProcedureConst is the `<Service><Method>Procedure` constant protoc-gen-connect-go declares for one RPC — both halves GoNames.
func ConnectServiceGoName ¶ added in v0.1.45
ConnectServiceGoName is the base protoc-gen-connect-go builds a service's identifiers from — the service's protogen GoName, i.e. GoCamelCase of the proto name. It declares `<base>Handler`, `Unimplemented<base>Handler`, `New<base>Handler`, `<base>Client` and `New<base>Client`: "Alphav1connectService" → Alphav1ConnectServiceHandler. Templates take this base and add the affix, exactly as the generator does.
The `…Name` constant is the one exception — see ConnectServiceNameConst.
func ConnectServiceNameConst ¶ added in v0.1.45
ConnectServiceNameConst is the constant protoc-gen-connect-go declares holding a service's fully-qualified name. Unlike every other identifier it declares, it is spelled from the RAW proto service name, not the GoName: "Alphav1connectService" → Alphav1connectServiceName (while the handler is Alphav1ConnectServiceHandler).
func EntityFieldName ¶
EntityFieldName returns the snake_case proto FIELD name that a CRUD create/get/update response uses to carry a single entity — the snake_case of the entity's (PascalCase) message name. For a "ModuleConfig" entity this is "module_config"; protoc-gen-go then generates the Go field "ModuleConfig".
This is the SINGLE source of truth for the entity-carrying field name, shared by three sites that MUST agree or multi-word CRUD silently breaks:
- the entity scaffolder (`forge scaffold entity`) emits `<Entity> <field> = 1;`,
- the CRUD ops emitter references the Go form (ToProtoPascalCase of this),
- the CRUD shape detector (validateCRUDShape) matches the response's observed snake_case field names against this.
The historical bug: the detector compared against strings.ToLower(Name) ("moduleconfig"), which matches neither the scaffolder's "module_config" nor the emitter's "ModuleConfig" — so every multi-word entity fell to the custom-read-shape stub and produced empty CRUD. Routing all three through this helper makes the concatenated-lowercase form impossible to reintroduce.
func EntityListFieldName ¶
EntityListFieldName returns the snake_case proto FIELD name that a CRUD list response uses to carry the repeated entity — the pluralized snake_case of the entity's message name. For "ModuleConfig" this is "module_configs". It is the plural companion to EntityFieldName; see that doc for why the derivation is centralized here.
func EsFieldName ¶ added in v0.1.45
EsFieldName is the property protoc-gen-es generates for a (non-oneof) message field — its localName: ProtoCamelCase, then the reserved property escape ("to_string" → "toString$"). Every TS reference to a generated message field (`item.fooBar`, `create(Schema, { fooBar })`) must use this.
func EsMethodName ¶ added in v0.1.45
EsMethodName is the localName protobuf-es gives an RPC — the key on a connect-es client (`client.listFoos`) and on a service's `method` map. It lower-cases the FIRST CHARACTER ONLY: "LLMChat" → "lLMChat", not "llmChat". A camel-caser that lowers a leading acronym run names a method the client does not have.
func FrontendServiceName ¶ added in v1.0.0
FrontendServiceName is the OpenTelemetry service.name a web frontend's browser telemetry reports: "<project>-<frontend>", e.g. "shop-web".
It is the ONE derivation of that name. The frontend's generated src/lib/otel_gen.ts bakes it as SERVICE_NAME (the value the runtime sends), and the generated observability — the frontend dashboard's scope and the frontend alert defaults — filters on it, so the two cannot name different services. An earlier split had the dashboards derive "<project>-frontend" while a hand-wired runtime reported "<project>-<frontend>", and every frontend alert matched nothing.
func GoCamelCase ¶ added in v0.1.45
GoCamelCase is protoc-gen-go's camel-casing of a proto name into a Go identifier — a verbatim port of google.golang.org/protobuf's internal/strs.GoCamelCase, which protogen applies to every message, enum, field, oneof, service and method name. protoc-gen-connect-go builds its identifiers from the same result (see ConnectServiceGoName, and ConnectServiceNameConst for its one exception).
Words begin at an underscore or an upper-case letter, and a digit is a word of its own, so the letter AFTER a digit is capitalised:
base64item → Base64Item (forge's old rule said Base64item)
alphav1connect → Alphav1Connect
address_line_2 → AddressLine_2 (an underscore before a non-lowercase
byte is kept)
_internal → XInternal (a leading underscore becomes X)
HTTPStatus → HTTPStatus (no initialism folding)
For a NESTED message or enum pass the name relative to its package with the dots kept ("Outer.Inner" → "Outer_Inner"): that is the string protogen camel-cases, and the dot handling below is what makes nested types come out joined by an underscore.
func GoFieldName ¶ added in v0.1.45
GoFieldName is GoFieldNames for the field called protoFieldName among siblings (the message's proto field names in declaration order). When protoFieldName is not among them — a caller that knows only the one field — it falls back to GoCamelCase, which is exact unless the field collides with a generated method or another field.
func GoFieldNames ¶ added in v0.1.45
GoFieldNames returns the Go struct field names protoc-gen-go generates for one message's fields, given every proto field name of that message in DECLARATION order.
It is GoCamelCase plus protogen's per-message conflict resolution: a field that would collide with a generated method (`descriptor` → Descriptor, `string` → String) or with an earlier field or its getter (`get_name` after `name`, whose getter is GetName) gets "_" appended until it is unique. Because that depends on the fields declared before it, a single field's Go name is only exact when computed from the whole list — which is why this takes the list rather than one name.
A real (non-synthetic) oneof also claims its own camel-cased name in the same table, after its first member. forge's generated surfaces never reference oneof groups; callers holding one should not assume this covers it.
func GoPackage ¶
GoPackage normalises a CLI/forge.yaml-style name into a Go package identifier in snake_case form. Hyphens convert to underscores; existing underscores are preserved; PascalCase / camelCase boundaries are split (so "AdminServer" → "admin_server", "calibrator_refit" stays "calibrator_refit", "admin-server" → "admin_server"). Distinct from ServicePackage in that it does NOT strip a trailing "Service" — callers using it for workers, operators, and arbitrary package leaves want the raw form.
Snake_case is a valid Go package identifier (Go's spec allows it, `golint` only warns), and matches the on-disk convention proto buf emits for multi-word proto packages (e.g. proto package `services.admin_server.v1` → directory `services/admin_server/v1`). Keeping the package name aligned with the proto path means handler dirs, generated mock files, and wire_gen function names all stay in lockstep without forcing the user to choose between snake on disk and compact in code.
func GoTypeName ¶ added in v0.1.45
GoTypeName is the Go identifier protoc-gen-go declares for a message or enum, given its fully-qualified proto name and the proto package of the file declaring it: "shop.v1.Order.Status" in "shop.v1" → "Order_Status". This is protogen's newGoIdent. A name outside protoPkg (or an empty package) is camel-cased whole, as protogen would for a package-less file.
func Humanize ¶ added in v0.1.45
Humanize splits a PascalCase/camelCase/snake_case identifier into space-separated words, preserving each word's case: "RetainedDatabases" -> "Retained Databases". For human-facing UI copy only; never for identifiers.
func HumanizeLower ¶ added in v0.1.45
HumanizeLower is Humanize lowercased as a whole: "retained databases".
func KCLIdentifier ¶ added in v0.1.45
KCLIdentifier returns the KCL variable name a workload is declared under in `deploy/kcl/workloads.k`.
The declaration is NAMED — `billing = fw.Workload {...}` rather than an anonymous list entry — because a name is what an environment refines: `wl.billing | {replicas = 3}`. An anonymous entry can only be reconstructed, which is the shape this vocabulary replaced.
KCL identifiers follow the same rules as Go's, so this is GoPackage: "billing-admin" -> billing_admin, "BillingService" -> billing_service. Kept as its own function rather than a call to GoPackage at each site so the INTENT is legible (this is a KCL binding, not a Go package) and so the two can diverge if KCL's identifier rules ever do.
func ProtoCamelCase ¶ added in v0.1.45
ProtoCamelCase is protobuf-es's protoCamelCase — the lowerCamelCase a field or oneof name takes in generated TypeScript (and protoc's default json_name). Unlike GoCamelCase it never capitalises after a digit and never keeps an underscore: "base64item" → "base64item", "address_line_2" → "addressLine2", "foo_bar" → "fooBar". The first letter keeps its case. Port of @bufbuild/protobuf reflect/names.ts.
func ProtoPackageBase ¶
ProtoPackageBase returns the proto package with any trailing version segment stripped — the version-INDEPENDENT logical package identity.
"billing.v1" -> "billing" "acme.billing.v1" -> "acme.billing" "billing" -> "billing" (already unversioned)
Pairing ProtoPackageBase + ProtoPackageVersion splits a fused `billing.v1` identity into ("billing", "v1") so the inventory can record the two distinctly. v2 then differs ONLY in Version, sharing the base — the data-model precondition for additive multi-version support.
func ProtoPackageVersion ¶
ProtoPackageVersion extracts the proto API version from a fully-qualified proto package name — the LAST dotted segment when it has the protobuf version shape `v<major>` optionally suffixed with a stability channel (`v1`, `v2`, `v1alpha1`, `v2beta3`). Returns "" when the package carries no version segment.
"billing.v1" -> "v1" "acme.billing.v1" -> "v1" "shop.v2beta1" -> "v2beta1" "billing" -> "" (unversioned) "" -> ""
This is the single source of truth for the version metadata the generated service inventory records (FORGE_SHAPE_REDESIGN — version-aware registry seam). It deliberately reads ONLY the package's own last segment, never the service name, so the inventory's Version field is exactly the proto API version and additive: a future `billing.v2` records Version "v2" as a second version of the same logical service rather than colliding identity.
func ServiceHookFile ¶
ServiceHookFile returns the canonical frontend hook filename for a service. Encodes the rule that the hook file is the service name in kebab-case (with initialisms kept glued) suffixed with `-hooks_gen.ts`.
The `_gen` is the convention, not decoration: this file is Tier-1 — it is hash-guarded and rewritten on every `forge generate` — and forge's rule is now mechanical, so that a reader can tell what forge owns from the filename alone rather than by opening the file and reading its banner.
All file-emitter sites AND the re-export indexer must go through this function. If a future caller needs a different suffix or extension, extract a parameterised helper rather than duplicating the kebab transform — the re-export index only stays in lockstep with on-disk filenames because both go through the same canonical splitter (`ToKebabCase`).
func ServiceHookFileLegacy ¶ added in v0.1.45
ServiceHookFileLegacy returns the PRE-`_gen` hook filename for a service.
It exists for exactly one caller: the retirement sweep that deletes the old copy out of a project generated before the rename. Nothing emits this name anymore.
func ServicePackage ¶
ServicePackage is the single canonical Go-package form for a service, binary, frontend, worker, or operator name.
Inputs accepted:
- Forge / CLI names: kebab-case ("admin-server"), snake_case ("admin_server"), or already-compact ("api").
- Proto service names: PascalCase ending in "Service" ("EchoService", "AdminServerService").
Output is always a single lowercase snake_case Go-style identifier. The PascalCase-with-"Service"-suffix branch first trims the suffix so "EchoService" -> "echo" and "AdminServerService" -> "admin_server". The pure-CLI branch normalises hyphens to underscores and PascalCase boundaries to underscores, so "admin-server" / "admin_server" / "AdminServer" all collapse to "admin_server".
One canonical function for every site that emits a handler dir, a generated mock file, a bootstrap import, or a wire alias — keep it the single source of truth so the on-disk dir layout, the codegen keys, and the cleanup sweeper can never disagree.
History: pre-2026-06-08 this function (and GoPackage) emitted compact form (separators stripped — "admin_server" → "adminserver"). The compact convention collided with the universal snake_case dir layout projects actually use (protoc-gen-go emits snake for multi-word proto packages, KCL package names use snake, every existing forge project on disk had snake handler dirs). The bug surface was a duplicate-dir failure where `forge generate` created `handlers/adminserver/` alongside the existing `handlers/admin_server/`, then regenerated wire_gen.go to reference the compact form while user-owned bootstrap.go still referenced the snake form — broken build.
func ToExportedFieldName ¶
ToExportedFieldName converts a lowercase package/field name to an exported Go identifier, respecting Go initialisms. Examples: "api" -> "API", "db" -> "DB", "orders" -> "Orders"
func ToKebabCase ¶
ToKebabCase converts a name to kebab-case, treating known initialisms (LLM, API, URL, JSON, etc. — see GoInitialisms) as a single segment so that "LLMGateway" → "llm-gateway" rather than "l-l-m-gateway".
Accepts input in PascalCase, camelCase, snake_case, or already-kebab- case form; the output is always lowercase, hyphen-separated, with no runs of multiple hyphens. This is the canonical kebab function for every site that emits filenames, slugs, route paths, or import paths that need to round-trip with proto Go names — keep it the single source of truth so frontend hooks files, navigation slugs, and re- export indexers can never disagree on whether "LLMGateway" splits as "l-l-m-gateway" or "llm-gateway".
func ToPascalCase ¶
ToPascalCase converts a hyphenated, underscored, or camelCase name to PascalCase. It handles both '-' and '_' as word separators and capitalizes Go initialisms. e.g. "api-gateway" -> "APIGateway", "user_service" -> "UserService", "http_client" -> "HTTPClient".
func ToSnakeCase ¶
ToSnakeCase converts a string (camelCase, PascalCase, UPPER_CASE, etc.) to snake_case. e.g. "firstName" -> "first_name", "HTTPStatus" -> "http_status", "UPPER_CASE" -> "upper_case".
func ToSnakeCaseWords ¶ added in v1.0.0
ToSnakeCaseWords is ToSnakeCase for labels a person reads — graph node names in logs, errors, the readiness report and the start plan — where the split must follow the WORDS of a Go identifier, not just its case changes:
GitHubAccessLoop → github_access_loop (ToSnakeCase: git_hub_access_loop) OAuthClient → oauth_client (o_auth_client) APIURL → api_url (apiurl) LLMMCPBridge → llm_mcp_bridge (llmmcp_bridge) UserIDs → user_ids (user_i_ds)
Three rules on top of ToSnakeCase's:
- a word written with inner capitals (CompoundWords: GitHub, OAuth, OpenAI, …) is one word, with any digits that follow it (OAuth2);
- a run of capitals that is a sequence of known initialisms (GoInitialisms, plus SnakeInitialisms) splits into them;
- a known initialism's plural (IDs, URLs, APIs) is one word.
Every other identifier gets exactly ToSnakeCase's answer, so the only names that change are the ones these rules name.
It is deliberately NOT ToSnakeCase itself. ToSnakeCase names wire and schema things — proto fields, columns, entity fields — whose spelling is a compatibility surface; a label is not.
func TripsReviveVarNaming ¶ added in v0.1.46
TripsReviveVarNaming reports whether revive's var-naming rule would want a different spelling of goIdent (e.g. CreateApiKey → CreateAPIKey). Proto / connect-generated handler method names must match the Handler interface exactly, so scaffolded stubs keep the generator spelling and suppress the lint instead of renaming.
Types ¶
This section is empty.