weegloo-codegen
v0.1.2
Published
Build-time TypeScript codegen for the Weegloo delivery APIs (CDA / ACDA). Turns ContentType definitions into typed response shapes + locale unwrap helpers.
Downloads
845
Maintainers
Readme
weegloo-codegen
Build-time TypeScript codegen for the Weegloo delivery APIs — Content Delivery API (CDA) and App Content Delivery API (ACDA). Turns ContentType definitions into typed response shapes.
CDA and ACDA return the same published response shape (same
?localemodes, sameincludemap). ACDA only narrows which items aServiceUsersees — never the shape of an item — so every generated type applies verbatim to both.
Status: experimental (0.x). API may shift between minor versions. This package emits types only — per ContentType it generates the delivery response shapes (single-locale
?locale={code}and all-locales?locale=*) and the management read shape (CMA / ACMA, see Managed shape). Locale unwrap and include-map resolution are left to the consumer (or an SDK layer).
Install
npm install --save-dev weegloo-codegen
# or: pnpm add -D weegloo-codegen
# or: yarn add -D weegloo-codegenOptional peer for nicer generated source formatting:
npm install --save-dev prettier # optional — auto-detected at runtimeTwo surfaces are exposed:
weegloo-codegen(CLI bin) — the generator.weegloo-codegen/runtime— runtime primitives (Refer,Media,PublishedContentSys,SingleResponseContent,ListResponseContent,SingleResponseContentAllLocales,ListResponseContentAllLocales,ManagedContentSys,SingleManagedResponse,ListManagedResponse,ContentMetadata,IncludeEntity,FieldTypes,LocaleWrap). Generated filesimport typefrom this subpath.
CLI
npx weegloo-codegen <input.json> [options]Arguments
| Argument | Required | Description |
| --------- | -------- | ----------------------------------------------------------- |
| <input> | yes | Path to ContentType JSON dump (CMA list, array, or single). |
Options
| Flag | Default | Description |
| ----------------- | -------------- | ------------------------------------------------------------------------------------- |
| -o, --out <dir> | ./generated | Output directory (created if absent). |
| --no-barrel | barrel emitted | Skip writing index.ts that re-exports every CT module. |
| --no-prettier | prettier on | Skip prettier post-processing. Required if prettier is not installed (auto-detected). |
| --header <text> | — | Header comment line(s) injected at the top of every emitted file. |
| --quiet | verbose | Suppress per-file wrote … lines and the trailing summary. |
| -h, --help | — | Print help. |
Exit codes
0— success.1— input read/parse failure, schema-shape error (e.g. unsupported field type), symbol-table error (name collision or invalid identifier), or--no-prettiernot set while prettier is missing.
Input shapes accepted
normalizeInput accepts three shapes so a CMA dump can be piped directly without reshaping:
- CMA list response:
{ items: ContentType[] } - Bare array:
ContentType[] - Single ContentType:
ContentType
In every case the CLI normalizes to an internal ContentType[] before rendering.
Sample input → output
Input (content-types.json):
{
"items": [
{
"sys": {
"id": "ct-post",
"type": "ContentType",
"version": 1,
"status": "Published"
},
"name": "Post",
"publishWithAuthor": false,
"fields": [
{
"id": "f1",
"name": "Title",
"apiName": "title",
"type": "ShortText",
"localized": false,
"required": true,
"validations": [],
"disabled": false
},
{
"id": "f2",
"name": "Author",
"apiName": "author",
"type": "Refer",
"targetType": "Content",
"localized": false,
"required": false,
"validations": [
{
"referContentType": [
{
"sys": {
"id": "ct-author",
"type": "Refer",
"targetType": "ContentType"
}
}
]
}
],
"disabled": false
}
]
}
]
}Emit (generated/Post.ts):
// AUTO-GENERATED by weegloo-codegen
// Source ContentType: sys.id = "ct-post", name = "Post"
// Do not edit — regenerate via `weegloo-codegen`.
//
// Response shape map:
// X / SingleXResponse / ListXResponse — CDA / ACDA `?locale={code}` (or omitted)
// XAllLocales / SingleXAllLocalesResponse / … — CDA / ACDA `?locale=*`
// XManaged / SingleXManagedResponse / … — CMA / ACMA content
import type {
ContentMetadata,
FieldTypes,
ListManagedResponse,
ListResponseContent,
ListResponseContentAllLocales,
LocaleWrap,
ManagedContentSys,
PublishedContentSys,
Refer,
SingleManagedResponse,
SingleResponseContent,
SingleResponseContentAllLocales,
} from "weegloo-codegen/runtime";
import type { Author } from "./Author";
// Single-locale shape (`?locale={code}` or omitted)
export interface PostFields {
title: FieldTypes.ShortText;
author?: Refer<"Content", Author> | undefined;
}
export interface Post {
sys: PublishedContentSys;
fields: PostFields;
}
export type SinglePostResponse = SingleResponseContent<Post>;
export type ListPostResponse = ListResponseContent<Post>;
// `?locale=*` shape — every field wrapped as LocaleWrap<T>
export interface PostFieldsAllLocales {
title: LocaleWrap<FieldTypes.ShortText>;
author?: LocaleWrap<Refer<"Content", Author>> | undefined;
}
export interface PostAllLocales {
sys: PublishedContentSys;
fields: PostFieldsAllLocales;
}
export type SinglePostAllLocalesResponse =
SingleResponseContentAllLocales<PostAllLocales>;
export type ListPostAllLocalesResponse =
ListResponseContentAllLocales<PostAllLocales>;
// Managed shape (CMA / ACMA) — sys/metadata envelope; fields reuse AllLocales
export interface PostManaged {
sys: ManagedContentSys;
fields: PostFieldsAllLocales;
metadata: ContentMetadata;
}
export type SinglePostManagedResponse = SingleManagedResponse<PostManaged>;
export type ListPostManagedResponse = ListManagedResponse<PostManaged>;Each CT file emits 11 type exports (no runtime helpers). A barrel generated/index.ts re-exports every emitted module alphabetically by TS name.
Field type mapping
| CMA field type | Emitted TS |
| ------------------------------------------- | --------------------------------------- |
| ShortText / LongText / RichText | FieldTypes.ShortText etc. (string) |
| Long / Number | FieldTypes.Long / Number (number) |
| Boolean | FieldTypes.Boolean |
| Date | FieldTypes.Date (string) |
| Location | FieldTypes.Location |
| Json | FieldTypes.Json |
| Refer → Media | Refer<"Media", Media> |
| Refer → Content (single-CT narrowed) | Refer<"Content", X> |
| Refer → Content (multi-CT narrowed) | Refer<"Content", X \| Y> |
| Refer → Content (no referContentType) | Refer<"Content"> (broad) |
| Array of ShortText | FieldTypes.ShortTextList (string[]) |
| Array of Refer → Media | Refer<"Media", Media>[] |
| Array of Refer → Content (narrowed) | Refer<"Content", X \| Y>[] |
| disabled: true | field skipped from Fields interface |
Required fields are emitted bare; non-required fields get ?: T | undefined (compatible with exactOptionalPropertyTypes).
apiName is used verbatim as the TS property key when it is a valid JS identifier and not a TS reserved word; otherwise it is quoted ("foo-bar", "class").
PublishedContentSys is emitted without a literal CT id generic argument — the generic defaults to string. This keeps a single generated artifact reusable across environments (dev/prod CT ids differ).
Two delivery call modes (CDA / ACDA)
Both modes apply identically to CDA (cda.weegloo.com) and ACDA (acda.weegloo.com) — the path examples below use a CDA URL, but the response shape (and therefore the generated type) is the same on ACDA.
?locale={code} (single locale)
Default mode. Main body fields and include map entities arrive as flat scalars.
import type { SinglePostResponse } from "./generated/Post";
const res = (await fetch(
"/v1/spaces/X/content-types/Y/contents/Z?locale=en-US&include=1",
).then((r) => r.json())) as SinglePostResponse;
const title: string = res.fields.title;?locale=* (all locales)
Main body and include entities arrive as LocaleWrap<T> per field.
import type { SinglePostAllLocalesResponse } from "./generated/Post";
const res = (await fetch(
"/v1/spaces/X/content-types/Y/contents/Z?locale=*",
).then((r) => r.json())) as SinglePostAllLocalesResponse;
const titleByLocale = res.fields.title; // LocaleWrap<string>
const enTitle: string | undefined = titleByLocale["en-US"];Locale unwrap (
LocaleWrap<T> → T) and include-map resolution are intentionally out of codegen scope. Use an SDK layer or hand-roll them as below.
Managed shape (CMA / ACMA)
The same ContentType also has a management read shape on CMA (cma.weegloo.com) and ACMA (acma.weegloo.com). Two things make it differ from delivery:
- Fields are always locale-wrapped. CMA/ACMA return every field as
{ [locale]: T }regardless of the field'slocalizedflag — identical to delivery?locale=*. SoXManagedreusesXFieldsAllLocalesforfields; there is no separate managed fields type. - The envelope is the authoring/draft view.
syscarries the resourceversion(for thex-weegloo-versionheader), the lifecyclestatus(Draft/Changed/Published/Archived), and apublishblock (absent until first publish). Items also carry top-levelmetadata.tags(Refer<"Tag">[]). The list wrapper'ssysis{ type: "TotalPageResponse" }(noid, unlike delivery).
import type { ListPostManagedResponse } from "./generated/Post";
const res = (await fetch("/v1/spaces/X/contents?include=1").then((r) =>
r.json(),
)) as ListPostManagedResponse;
for (const item of res.items) {
const status = item.sys.status; // "Draft" | "Changed" | "Published" | "Archived"
const enTitle = item.fields.title["en-US"]; // LocaleWrap<string>
const tagIds = item.metadata.tags.map((t) => t.sys.id);
}CMA and ACMA content responses share this shape —
XManagedapplies verbatim to both. (ACMA's authortargetTypemay beServiceUserrather thanUser; the runtime type covers both.)
Resolving includes by hand
response.include is typed as Record<string, IncludeEntity[]> — IncludeEntity is raw { sys: { id; type; contentType? } }. To resolve a link to its full entity, look up by sys.id and verify by sys.contentType.sys.id, then cast to the sibling CT type:
import type { IncludeEntity, Refer } from "weegloo-codegen/runtime";
import type { Author, SinglePostResponse } from "./generated";
function lookupAuthor(
ref: Refer<"Content", Author> | undefined,
res: SinglePostResponse,
): Author | undefined {
if (!ref || !res.include) return undefined;
for (const bucket of Object.values(res.include)) {
for (const entity of bucket) {
const e: IncludeEntity = entity;
if (
e.sys.id === ref.sys.id &&
e.sys.contentType?.sys.id === "ct-author"
) {
return e as unknown as Author;
}
}
}
return undefined;
}?select= query is not represented at the type level — generated types still claim every field exists. Treat the response as Partial<...> and use optional chaining when you select a subset.
Export matrix (per CT)
Each emitted .ts file produces 11 type exports and 0 runtime helpers:
| Group | Count | Names |
| -------------------- | ----- | ------------------------------------------------------------------------------------------ |
| Single-locale | 4 | XFields, X, SingleXResponse, ListXResponse |
| ?locale=* | 4 | XFieldsAllLocales, XAllLocales, SingleXAllLocalesResponse, ListXAllLocalesResponse |
| Managed (CMA / ACMA) | 3 | XManaged, SingleXManagedResponse, ListXManagedResponse |
Strict tsconfig compatibility
Generated code is compatible with the strictest TypeScript settings:
strict: truenoUnusedLocals: truenoUnusedParameters: trueexactOptionalPropertyTypes: true
Verified against the fixture-11 sanitized real-space dump under an isolated tsconfig.
Local development
pnpm --filter weegloo-codegen build
pnpm --filter weegloo-codegen test:run
pnpm --filter weegloo-codegen type-check
pnpm --filter weegloo-codegen lintE2E tests (src/cli.test.ts, src/consumer.type-check.test.ts) invoke pnpm build in their beforeAll so they always pick up source changes — slow but unavoidable for a binary.
License
MIT
