@backmatter/core 0.0.27 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backmatter/core",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "description": "Experimental Backmatter SDK. APIs will break. Not ready for production.",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/api-loader.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  // packages/core/src/api-loader.ts
2
2
  import type { BackmatterDoc, BackmatterDocSummary } from "./doc";
3
- import { encodeDocLocale } from "./path";
4
3
 
5
4
  export type PublicDocsQuery = {
6
5
  branch?: string;
7
6
  type?: string;
8
- locale?: string;
9
7
  /** When set, Next Data Cache uses these tags instead of `cache: "no-store"`. */
10
8
  tags?: string[];
11
9
  };
@@ -31,7 +29,6 @@ function publicDocsUrl(
31
29
  );
32
30
  if (query.branch) url.searchParams.set("branch", query.branch);
33
31
  if (query.type) url.searchParams.set("type", query.type);
34
- if (query.locale) url.searchParams.set("locale", query.locale);
35
32
  return url.toString();
36
33
  }
37
34
 
@@ -55,13 +52,12 @@ export async function getDocFromApi(
55
52
  apiUrl: string,
56
53
  workspaceId: string,
57
54
  contentToken: string | undefined,
58
- locale: string,
59
55
  type: string,
60
56
  slug: string,
61
57
  query: Pick<PublicDocsQuery, "branch" | "tags"> = {},
62
58
  ): Promise<BackmatterDoc | null> {
63
59
  const res = await fetch(
64
- publicDocsUrl(apiUrl, workspaceId, `/${encodeDocLocale(locale)}/${type}/${slug}`, query),
60
+ publicDocsUrl(apiUrl, workspaceId, `/${type}/${slug}`, query),
65
61
  fetchInit(contentToken, query.tags),
66
62
  );
67
63
  if (res.status === 404) return null;
package/src/config.ts CHANGED
@@ -48,16 +48,11 @@ export type BackmatterConfig = {
48
48
  siteUrl?: string;
49
49
  /** Site root for filesystem mode. Default: process.cwd(). */
50
50
  siteRoot?: string;
51
- /** Locales the catch-all route accepts in `{locale}`. Unset → any segment. */
52
- locales?: readonly string[];
53
- /** Locale used by routes without a `{locale}` placeholder. Default: "en". */
54
- defaultLocale?: string;
55
51
  dirs?: BackmatterDirs;
56
52
  devBar?: DevBarHostConfig;
57
53
  };
58
54
 
59
- export type ResolvedConfig = Omit<BackmatterConfig, "dirs" | "defaultLocale"> & {
60
- defaultLocale: string;
55
+ export type ResolvedConfig = Omit<BackmatterConfig, "dirs"> & {
61
56
  dirs: Required<BackmatterDirs>;
62
57
  };
63
58
 
@@ -70,7 +65,6 @@ export const DEFAULT_DIRS: Required<BackmatterDirs> = {
70
65
  objects: "objects",
71
66
  sources: "sources",
72
67
  };
73
- export const DEFAULT_LOCALE = "en";
74
68
  export const CLOUD_API_URL = "https://api.backmatter.app";
75
69
  export const CLOUD_DASHBOARD_URL = "https://dash.backmatter.app";
76
70
  export const LOCAL_DASHBOARD_URL = "http://localhost:5173";
@@ -189,12 +183,6 @@ export function detectSiteUrl(
189
183
 
190
184
  /** Identity helper that gives `backmatter.config.ts` a typed default export. */
191
185
  export function defineConfig(config: BackmatterConfig): BackmatterConfig {
192
- if (config.defaultLocale !== undefined && !config.defaultLocale) {
193
- throw new Error("config.defaultLocale must be a non-empty string");
194
- }
195
- if (config.locales && config.defaultLocale && !config.locales.includes(config.defaultLocale)) {
196
- throw new Error(`config.defaultLocale "${config.defaultLocale}" is not in config.locales`);
197
- }
198
186
  return config;
199
187
  }
200
188
 
@@ -218,8 +206,6 @@ export function resolveConfig(
218
206
  branch: config.branch?.trim() || detectGitBranch(env) || undefined,
219
207
  siteUrl: detectSiteUrl(config, env),
220
208
  siteRoot: config.siteRoot,
221
- locales: config.locales,
222
- defaultLocale: config.defaultLocale ?? DEFAULT_LOCALE,
223
209
  devBar: config.devBar,
224
210
  dirs: {
225
211
  models: config.dirs?.models ?? DEFAULT_DIRS.models,
@@ -10,7 +10,6 @@ export const BACKMATTER_REVALIDATE_PATH = "/api/backmatter/revalidate";
10
10
  export const BACKMATTER_CACHE_TAG = "backmatter";
11
11
 
12
12
  export type ContentCommittedDoc = {
13
- locale: string;
14
13
  type: string;
15
14
  slug: string;
16
15
  };
@@ -29,11 +28,8 @@ export function revalidateUrl(siteUrl: string): string {
29
28
  return `${siteUrl.replace(/\/$/, "")}${BACKMATTER_REVALIDATE_PATH}`;
30
29
  }
31
30
 
32
- export function contentCacheTags(locale: string, type: string, slug: string): string[] {
33
- const identity = locale
34
- ? `backmatter:${locale}:${type}:${slug}`
35
- : `backmatter:${type}:${slug}`;
36
- return [BACKMATTER_CACHE_TAG, `backmatter:${type}`, identity];
31
+ export function contentCacheTags(type: string, slug: string): string[] {
32
+ return [BACKMATTER_CACHE_TAG, `backmatter:${type}`, `backmatter:${type}:${slug}`];
37
33
  }
38
34
 
39
35
  export function listCacheTags(type?: string): string[] {
@@ -49,7 +45,7 @@ export function revalidateTagsForCommit(event: Pick<ContentCommittedEvent, "docs
49
45
  if (event.full) return [BACKMATTER_CACHE_TAG];
50
46
  const tags = new Set<string>();
51
47
  for (const doc of event.docs ?? []) {
52
- for (const tag of contentCacheTags(doc.locale, doc.type, doc.slug)) {
48
+ for (const tag of contentCacheTags(doc.type, doc.slug)) {
53
49
  if (tag !== BACKMATTER_CACHE_TAG) tags.add(tag);
54
50
  }
55
51
  }
@@ -67,7 +63,7 @@ export function pathsForCommittedDocs(
67
63
  for (const doc of docs) {
68
64
  const model = byType.get(doc.type);
69
65
  if (!model) continue;
70
- const path = routeFor(model, doc.locale, doc.slug);
66
+ const path = routeFor(model, doc.slug);
71
67
  if (!path) continue;
72
68
  if (seen.has(path)) continue;
73
69
  seen.add(path);
package/src/doc-ref.ts CHANGED
@@ -1,6 +1,6 @@
1
+ // packages/core/src/doc-ref.ts
1
2
  export type DocRef = {
2
3
  workspaceId: string;
3
- locale: string;
4
4
  type: string;
5
5
  slug: string;
6
6
  };
@@ -15,12 +15,8 @@ export function parseDocRef(input: unknown): DocRef {
15
15
  throw new Error(`docRef.${key} is required`);
16
16
  }
17
17
  }
18
- if (typeof value.locale !== "string") {
19
- throw new Error("docRef.locale is required");
20
- }
21
18
  return {
22
19
  workspaceId: value.workspaceId as string,
23
- locale: value.locale as string,
24
20
  type: value.type as string,
25
21
  slug: value.slug as string,
26
22
  };
@@ -28,7 +24,5 @@ export function parseDocRef(input: unknown): DocRef {
28
24
 
29
25
  /** Relative content path (no workspace prefix) used under head/ and trees/{userId}/. */
30
26
  export function docRefPath(ref: DocRef): string {
31
- return ref.locale
32
- ? `${ref.locale}/${ref.type}/${ref.slug}`
33
- : `${ref.type}/${ref.slug}`;
27
+ return `${ref.type}/${ref.slug}`;
34
28
  }
package/src/doc.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  // packages/core/src/doc.ts
2
2
 
3
3
  export type BackmatterDocSummary = {
4
- locale: string;
5
4
  type: string;
6
5
  slug: string;
7
6
  /** Card-level fields when the loader already parsed the file. */
@@ -9,7 +8,6 @@ export type BackmatterDocSummary = {
9
8
  };
10
9
 
11
10
  export type BackmatterDoc = {
12
- locale: string;
13
11
  type: string;
14
12
  slug: string;
15
13
  /** `head` = committed on the branch; `tree` = the caller's uncommitted working copy. */
package/src/fields.ts CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  export type FieldKind =
4
4
  | "text"
5
- | "locale"
6
5
  | "image"
7
6
  | "media"
8
7
  | "relation"
@@ -77,7 +76,6 @@ function composite<K extends "object" | "array", R extends boolean>(
77
76
 
78
77
  export const field = {
79
78
  text: <const R extends boolean = false>(opts?: Opts<R>) => base("text", opts),
80
- locale: <const R extends boolean = false>(opts?: Opts<R>) => base("locale", opts),
81
79
  image: <const R extends boolean = false>(opts?: Opts<R>) => base("image", opts),
82
80
  media: <const R extends boolean = false>(opts?: Opts<R>) => base("media", opts),
83
81
  number: <const R extends boolean = false>(opts?: Opts<R>) => base("number", opts),
@@ -117,7 +115,6 @@ export function parseFieldSnapshot(input: unknown): FieldSnapshot {
117
115
  const kind = value.kind;
118
116
  const allowed: FieldKind[] = [
119
117
  "text",
120
- "locale",
121
118
  "image",
122
119
  "media",
123
120
  "relation",
package/src/index.ts CHANGED
@@ -75,13 +75,12 @@ export {
75
75
  resolveDevBarApiUrl,
76
76
  resolveDashboardUrl,
77
77
  DEFAULT_DIRS,
78
- DEFAULT_LOCALE,
79
78
  CLOUD_API_URL,
80
79
  CLOUD_DASHBOARD_URL,
81
80
  LOCAL_DASHBOARD_URL,
82
81
  } from "./config";
83
82
 
84
- export type { RouteMatch, RouteModel, MatchRouteOptions } from "./routes";
83
+ export type { RouteMatch, RouteModel } from "./routes";
85
84
  export {
86
85
  DEFAULT_ROUTE,
87
86
  compileRoutePattern,
@@ -110,9 +109,6 @@ export {
110
109
  repoPathFromModel,
111
110
  contentExtFromPath,
112
111
  contentExtForType,
113
- EMPTY_LOCALE_SEGMENT,
114
- encodeDocLocale,
115
- decodeDocLocale,
116
112
  encodeGitBranch,
117
113
  decodeGitBranch,
118
114
  s3HeadKey,
package/src/model.ts CHANGED
@@ -13,17 +13,16 @@ import type { DocFormat } from "./doc-format";
13
13
 
14
14
  export type ModelDef = {
15
15
  type: string;
16
- /** Repo-relative path template, e.g. content/{locale}/blog/{slug}.mdx */
16
+ /** Repo-relative path template, e.g. content/blog/{slug}.mdx */
17
17
  path: string;
18
18
  /**
19
19
  * URL template for the host catch-all route, e.g. "/blog/{slug}".
20
- * Defaults to "/{locale}/{type}/{slug}" when omitted (see routes.ts).
20
+ * Defaults to "/{type}/{slug}" when omitted (see routes.ts).
21
21
  * `false` excludes the model from the catch-all. Singletons default to `false`.
22
22
  */
23
23
  route?: string | false;
24
24
  /**
25
- * At most one document for each identity the path allows (one file, or one
26
- * file per `{locale}` when that placeholder is present).
25
+ * At most one document for each identity the path allows.
27
26
  */
28
27
  singleton?: boolean;
29
28
  /** File format. Default `"mdx"`. `"page"` stores JSON with a blocks array. */
@@ -79,8 +78,8 @@ export function resolveTemplateSlug(
79
78
  }
80
79
 
81
80
  /** Default repo path when a model does not declare one. */
82
- export const DEFAULT_MODEL_PATH = "content/{locale}/{type}/{slug}.mdx";
83
- export const DEFAULT_PAGE_PATH = "content/{locale}/{type}/{slug}.json";
81
+ export const DEFAULT_MODEL_PATH = "content/{type}/{slug}.mdx";
82
+ export const DEFAULT_PAGE_PATH = "content/{type}/{slug}.json";
84
83
 
85
84
  export function defaultModelPath(type: string, format: DocFormat = "mdx"): string {
86
85
  const template = format === "page" ? DEFAULT_PAGE_PATH : DEFAULT_MODEL_PATH;
@@ -103,6 +102,9 @@ export type ModelSnapshot = {
103
102
  export const IMPLICIT_SINGLETON_SLUG = "index";
104
103
 
105
104
  export function assertModelPath(path: string, singleton: boolean, label = "model.path"): void {
105
+ if (path.includes("{locale}")) {
106
+ throw new Error(`${label} must not contain {locale}`);
107
+ }
106
108
  if (!singleton && !path.includes("{slug}")) {
107
109
  throw new Error(`${label} must include {slug}`);
108
110
  }
@@ -117,6 +119,9 @@ export function assertModelRoute(
117
119
  if (typeof route !== "string" || !route.startsWith("/")) {
118
120
  throw new Error(`${label} must start with "/"`);
119
121
  }
122
+ if (route.includes("{locale}")) {
123
+ throw new Error(`${label} must not contain {locale}`);
124
+ }
120
125
  if (!singleton && !route.includes("{slug}")) {
121
126
  throw new Error(`${label} must include {slug}`);
122
127
  }
@@ -140,7 +145,7 @@ type WithTemplate<D extends ModelInput> = D extends { external: true }
140
145
 
141
146
  /**
142
147
  * Validates the definition at runtime and preserves its literal types.
143
- * `path` defaults to `content/{locale}/{type}/{slug}.mdx`.
148
+ * `path` defaults to `content/{type}/{slug}.mdx`.
144
149
  */
145
150
  export function defineModel<const D extends ModelInput>(def: D): WithTemplate<WithPath<D>> {
146
151
  if (!def.type || typeof def.type !== "string") {
package/src/node/cli.ts CHANGED
@@ -138,7 +138,7 @@ export async function loadModels(root: string, modelsPath?: string): Promise<Mod
138
138
 
139
139
  /**
140
140
  * Static directory roots the models' path templates live under, e.g.
141
- * `content/{locale}/blog/{slug}.mdx` → `content`. Roots that are sub-paths of
141
+ * `content/en/blog/{slug}.mdx` → `content/en`. Roots that are sub-paths of
142
142
  * another root are dropped; a template with no static leading segment yields ".".
143
143
  */
144
144
  export function contentRoots(models: ModelSnapshot[]): string[] {
@@ -257,13 +257,11 @@ import { defineConfig } from "${from}";
257
257
 
258
258
  // General settings only. Models live in models/*.ts, composer blocks in
259
259
  // blocks/*.tsx, in-content blocks in richtext/*.tsx, MDX tags in mdx/*.tsx,
260
- // and content in content/{locale}/{type}/{slug}.{mdx,json} — all discovered
261
- // automatically.
260
+ // and content under each model's path template — all discovered automatically.
262
261
  // Set workspaceId from General settings (not an env var).
263
262
  // API URL defaults to https://api.backmatter.app in production; tokens stay env.
264
263
  export default defineConfig({
265
264
  // workspaceId: "ws_…",
266
- defaultLocale: "en",
267
265
  });
268
266
  `;
269
267
  }
@@ -36,13 +36,8 @@ export type CreateBackmatterOptions<Models extends readonly ModelDef[] = readonl
36
36
  export type BackmatterClient<Models extends readonly ModelDef[] = readonly ModelDef[]> = {
37
37
  listDocs: (filter?: {
38
38
  type?: string;
39
- locale?: string;
40
39
  }) => Promise<BackmatterDocSummary[]>;
41
- getDoc: (
42
- locale: string,
43
- type: string,
44
- slug: string,
45
- ) => Promise<BackmatterDoc | null>;
40
+ getDoc: (type: string, slug: string) => Promise<BackmatterDoc | null>;
46
41
  /** Typed access keyed by model.type: cms.query.blog.get / findMany */
47
42
  query: Query<Models>;
48
43
  /** The registered models, e.g. for route matching. */
@@ -109,26 +104,24 @@ export function createBackmatter<const Models extends readonly ModelDef[]>(
109
104
  ? await listDocsFromApi(apiUrl!, workspaceId!, contentToken, {
110
105
  branch,
111
106
  type: filter?.type,
112
- locale: filter?.locale,
113
107
  tags: taggedApiFetch ? listCacheTags(filter?.type) : undefined,
114
108
  })
115
109
  : await listDocsFromFs(siteRoot, models);
116
110
 
117
111
  return docs.filter((d) => {
118
112
  if (filter?.type && d.type !== filter.type) return false;
119
- if (filter?.locale && d.locale !== filter.locale) return false;
120
113
  return true;
121
114
  });
122
115
  },
123
116
 
124
- async getDoc(locale, type, slug) {
117
+ async getDoc(type, slug) {
125
118
  if (useApi) {
126
- return getDocFromApi(apiUrl!, workspaceId!, contentToken, locale, type, slug, {
119
+ return getDocFromApi(apiUrl!, workspaceId!, contentToken, type, slug, {
127
120
  branch,
128
- tags: taggedApiFetch ? contentCacheTags(locale, type, slug) : undefined,
121
+ tags: taggedApiFetch ? contentCacheTags(type, slug) : undefined,
129
122
  });
130
123
  }
131
- return getDocFromFs(siteRoot, models, locale, type, slug);
124
+ return getDocFromFs(siteRoot, models, type, slug);
132
125
  },
133
126
  };
134
127
 
@@ -63,15 +63,14 @@ export async function listDocsFromFs(
63
63
  }
64
64
  }
65
65
  docs.push({
66
- locale: hit.locale ?? "",
67
66
  type: hit.type,
68
67
  slug: hit.slug,
69
68
  frontmatter,
70
69
  });
71
70
  }
72
71
  return docs.sort((a, b) => {
73
- const ak = `${a.locale}/${a.type}/${a.slug}`;
74
- const bk = `${b.locale}/${b.type}/${b.slug}`;
72
+ const ak = `${a.type}/${a.slug}`;
73
+ const bk = `${b.type}/${b.slug}`;
75
74
  return ak.localeCompare(bk);
76
75
  });
77
76
  }
@@ -79,13 +78,12 @@ export async function listDocsFromFs(
79
78
  export async function getDocFromFs(
80
79
  siteRoot: string,
81
80
  models: ModelSnapshot[],
82
- locale: string,
83
81
  type: string,
84
82
  slug: string,
85
83
  ): Promise<BackmatterDoc | null> {
86
84
  const model = models.find((m) => m.type === type);
87
85
  if (!model) return null;
88
- const rel = repoPathFromModel(model, locale, slug);
86
+ const rel = repoPathFromModel(model, slug);
89
87
  const full = path.join(siteRoot, rel);
90
88
  let raw: string;
91
89
  try {
@@ -95,7 +93,6 @@ export async function getDocFromFs(
95
93
  }
96
94
  const parsed = parseDoc(raw, modelFormat(model));
97
95
  return {
98
- locale,
99
96
  type,
100
97
  slug,
101
98
  source: "head",
@@ -105,27 +105,27 @@ function renderCachedCmsClient(): string[] {
105
105
  return [
106
106
  "const raw = createBackmatter({ ...config, models, sync: false });",
107
107
  "",
108
- "async function cachedGetDoc(locale: string, type: string, slug: string) {",
108
+ "async function cachedGetDoc(type: string, slug: string) {",
109
109
  ' "use cache";',
110
110
  ' cacheLife("hours");',
111
- " cacheTag(...contentCacheTags(locale, type, slug));",
112
- " return raw.getDoc(locale, type, slug);",
111
+ " cacheTag(...contentCacheTags(type, slug));",
112
+ " return raw.getDoc(type, slug);",
113
113
  "}",
114
114
  "",
115
- "async function cachedListDocs(filter?: { type?: string; locale?: string }) {",
115
+ "async function cachedListDocs(filter?: { type?: string }) {",
116
116
  ' "use cache";',
117
117
  ' cacheLife("hours");',
118
118
  " cacheTag(...listCacheTags(filter?.type));",
119
119
  " return raw.listDocs(filter);",
120
120
  "}",
121
121
  "",
122
- "async function getDoc(locale: string, type: string, slug: string) {",
122
+ "async function getDoc(type: string, slug: string) {",
123
123
  " const preview = currentPreviewContent();",
124
- " if (preview) return preview.getDoc(locale, type, slug);",
125
- " return cachedGetDoc(locale, type, slug);",
124
+ " if (preview) return preview.getDoc(type, slug);",
125
+ " return cachedGetDoc(type, slug);",
126
126
  "}",
127
127
  "",
128
- "async function listDocs(filter?: { type?: string; locale?: string }) {",
128
+ "async function listDocs(filter?: { type?: string }) {",
129
129
  " const preview = currentPreviewContent();",
130
130
  " if (preview) return preview.listDocs(filter);",
131
131
  " return cachedListDocs(filter);",
package/src/page-nav.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  import { isRoutable, routeFor, type RouteModel } from "./routes";
3
3
 
4
4
  export type PageNavDoc = {
5
- locale: string;
6
5
  type: string;
7
6
  slug: string;
8
7
  };
@@ -21,22 +20,19 @@ export function pageNavItems(input: {
21
20
  current: PageNavDoc | null;
22
21
  }): PageNavItem[] {
23
22
  const byType = new Map(input.models.map((model) => [model.type, model]));
24
- const locales = new Set(input.docs.map((doc) => doc.locale));
25
- const showLocale = locales.size > 1;
26
23
  const items: PageNavItem[] = [];
27
24
  for (const doc of input.docs) {
28
25
  const model = byType.get(doc.type);
29
26
  if (!model || !isRoutable(model)) continue;
30
- const href = routeFor(model, doc.locale, doc.slug);
27
+ const href = routeFor(model, doc.slug);
31
28
  if (!href) continue;
32
29
  const name = `${doc.type}/${doc.slug}`;
33
30
  items.push({
34
- key: `${doc.locale}/${doc.type}/${doc.slug}`,
35
- label: showLocale ? `${name} · ${doc.locale}` : name,
31
+ key: `${doc.type}/${doc.slug}`,
32
+ label: name,
36
33
  href,
37
34
  current: Boolean(
38
35
  input.current &&
39
- input.current.locale === doc.locale &&
40
36
  input.current.type === doc.type &&
41
37
  input.current.slug === doc.slug,
42
38
  ),
package/src/path.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  import { IMPLICIT_SINGLETON_SLUG, type ModelSnapshot } from "./model";
3
3
 
4
4
  export type PathMatch = {
5
- locale?: string;
6
5
  slug: string;
7
6
  type: string;
8
7
  };
@@ -11,13 +10,15 @@ function escapeRegex(s: string): string {
11
10
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12
11
  }
13
12
 
14
- /** Compile a model path template into a regex with named locale/slug captures. */
13
+ /** Compile a model path template into a regex with a named slug capture. */
15
14
  export function compilePathPattern(template: string): RegExp {
16
- const parts = template.split(/(\{locale\}|\{slug\})/g);
15
+ if (template.includes("{locale}")) {
16
+ throw new Error("model.path must not contain {locale}");
17
+ }
18
+ const parts = template.split(/(\{slug\})/g);
17
19
  let pattern = "^";
18
20
  for (const part of parts) {
19
- if (part === "{locale}") pattern += "(?<locale>[^/]+)";
20
- else if (part === "{slug}") pattern += "(?<slug>[^/]+?)";
21
+ if (part === "{slug}") pattern += "(?<slug>[^/]+?)";
21
22
  else pattern += escapeRegex(part);
22
23
  }
23
24
  pattern += "$";
@@ -32,11 +33,8 @@ export function matchModelPath(
32
33
  const re = compilePathPattern(model.path);
33
34
  const m = re.exec(normalized);
34
35
  if (!m) return null;
35
- const hasLocale = model.path.includes("{locale}");
36
36
  const hasSlug = model.path.includes("{slug}");
37
- if (hasLocale && !m.groups?.locale) return null;
38
37
  if (hasSlug && !m.groups?.slug) return null;
39
- // Strip trailing extension from slug if the template baked it into the capture.
40
38
  let slug = hasSlug ? m.groups!.slug! : IMPLICIT_SINGLETON_SLUG;
41
39
  const ext = contentExtFromPath(model.path);
42
40
  const suffix = `.${ext}`;
@@ -44,9 +42,7 @@ export function matchModelPath(
44
42
  slug = slug.slice(0, -suffix.length);
45
43
  }
46
44
  if (!slug) return null;
47
- const match: PathMatch = { slug, type: model.type };
48
- if (hasLocale) match.locale = m.groups!.locale;
49
- return match;
45
+ return { slug, type: model.type };
50
46
  }
51
47
 
52
48
  export function matchAnyModelPath(
@@ -75,23 +71,17 @@ export function contentExtForType(
75
71
 
76
72
  export function repoPathFromModel(
77
73
  model: Pick<ModelSnapshot, "path">,
78
- locale: string | undefined,
79
74
  slug: string,
80
75
  ): string {
81
- let out = model.path;
82
- if (locale !== undefined) out = out.replaceAll("{locale}", locale);
83
- return out.replaceAll("{slug}", slug);
76
+ return model.path.replaceAll("{slug}", slug);
84
77
  }
85
78
 
86
79
  // ---------------------------------------------------------------------------
87
80
  // S3 layout (git working tree model)
88
81
  //
89
- // workspaces/{ws}/branches/{branch}/head/{locale}/{type}/{slug}.mdx
90
- // mirror of the git branch HEAD — what the public site reads
91
- // workspaces/{ws}/branches/{branch}/trees/{userId}/{locale}/{type}/{slug}.mdx
92
- // one user's uncommitted file on that branch
93
- // workspaces/{ws}/branches/{branch}/meta/shas/{locale}/{type}/{slug}.json
94
- // HEAD blob sha per file (conflict base)
82
+ // workspaces/{ws}/branches/{branch}/head/{type}/{slug}.mdx
83
+ // workspaces/{ws}/branches/{branch}/trees/{userId}/{type}/{slug}.mdx
84
+ // workspaces/{ws}/branches/{branch}/meta/shas/{type}/{slug}.json
95
85
  // ---------------------------------------------------------------------------
96
86
 
97
87
  /** Where a doc's bytes came from: the branch HEAD mirror or the caller's working tree. */
@@ -121,55 +111,39 @@ export function s3TreePrefix(
121
111
  return `${branchRoot(workspaceId, branch)}/trees/${encodeURIComponent(userId)}/`;
122
112
  }
123
113
 
124
- /** URL stand-in when a model path has no `{locale}` — not a language code. */
125
- export const EMPTY_LOCALE_SEGMENT = "-";
126
-
127
- export function encodeDocLocale(locale: string | undefined): string {
128
- return locale ? locale : EMPTY_LOCALE_SEGMENT;
129
- }
130
-
131
- export function decodeDocLocale(segment: string): string {
132
- return segment === EMPTY_LOCALE_SEGMENT ? "" : segment;
133
- }
134
-
135
- function contentLeaf(type: string, slug: string, ext: string, locale?: string): string {
136
- const leaf = `${type}/${slug}.${ext}`;
137
- return locale ? `${locale}/${leaf}` : leaf;
114
+ function contentLeaf(type: string, slug: string, ext: string): string {
115
+ return `${type}/${slug}.${ext}`;
138
116
  }
139
117
 
140
118
  export function s3HeadKey(
141
119
  workspaceId: string,
142
120
  branch: string,
143
- locale: string | undefined,
144
121
  type: string,
145
122
  slug: string,
146
123
  ext = "mdx",
147
124
  ): string {
148
- return `${s3HeadPrefix(workspaceId, branch)}${contentLeaf(type, slug, ext, locale || undefined)}`;
125
+ return `${s3HeadPrefix(workspaceId, branch)}${contentLeaf(type, slug, ext)}`;
149
126
  }
150
127
 
151
128
  export function s3TreeKey(
152
129
  workspaceId: string,
153
130
  branch: string,
154
131
  userId: string,
155
- locale: string | undefined,
156
132
  type: string,
157
133
  slug: string,
158
134
  ext = "mdx",
159
135
  ): string {
160
- return `${s3TreePrefix(workspaceId, branch, userId)}${contentLeaf(type, slug, ext, locale || undefined)}`;
136
+ return `${s3TreePrefix(workspaceId, branch, userId)}${contentLeaf(type, slug, ext)}`;
161
137
  }
162
138
 
163
139
  /** HEAD blob sha for conflict checks. */
164
140
  export function s3ShaMetaKey(
165
141
  workspaceId: string,
166
142
  branch: string,
167
- locale: string | undefined,
168
143
  type: string,
169
144
  slug: string,
170
145
  ): string {
171
- const leaf = locale ? `${locale}/${type}/${slug}.json` : `${type}/${slug}.json`;
172
- return `${branchRoot(workspaceId, branch)}/meta/shas/${leaf}`;
146
+ return `${branchRoot(workspaceId, branch)}/meta/shas/${type}/${slug}.json`;
173
147
  }
174
148
 
175
149
  export type ParsedContentKey = {
@@ -177,7 +151,6 @@ export type ParsedContentKey = {
177
151
  source: ContentSource;
178
152
  /** Owner of the working tree; null for head keys. */
179
153
  userId: string | null;
180
- locale?: string;
181
154
  type: string;
182
155
  slug: string;
183
156
  };
@@ -219,11 +192,8 @@ export function parseS3ContentKey(
219
192
  return null;
220
193
  }
221
194
 
222
- function parseContentLeaf(leaf: string): { locale?: string; type: string; slug: string } | null {
195
+ function parseContentLeaf(leaf: string): { type: string; slug: string } | null {
223
196
  const parts = leaf.split("/");
224
- if (parts.length === 3) {
225
- return { locale: parts[0], type: parts[1]!, slug: parts[2]! };
226
- }
227
197
  if (parts.length === 2) {
228
198
  return { type: parts[0]!, slug: parts[1]! };
229
199
  }
@@ -9,7 +9,6 @@ import { parsePreviewHotkey, type PreviewHotkeyMessage } from "./preview-hotkeys
9
9
  export const PREVIEW_CHANNEL_VERSION = 1 as const;
10
10
 
11
11
  export type PreviewChannelRef = {
12
- locale: string;
13
12
  type: string;
14
13
  slug: string;
15
14
  branch?: string;
@@ -90,7 +89,7 @@ export type PreviewTransport = {
90
89
  };
91
90
 
92
91
  export function samePreviewRef(a: PreviewChannelRef, b: PreviewChannelRef): boolean {
93
- if (a.locale !== b.locale || a.type !== b.type || a.slug !== b.slug) return false;
92
+ if (a.type !== b.type || a.slug !== b.slug) return false;
94
93
  if (a.branch && b.branch && a.branch !== b.branch) return false;
95
94
  if (a.tree && b.tree && a.tree !== b.tree) return false;
96
95
  return true;
@@ -102,11 +101,9 @@ function isRecord(value: unknown): value is Record<string, unknown> {
102
101
 
103
102
  function readRef(value: unknown): PreviewChannelRef | null {
104
103
  if (!isRecord(value)) return null;
105
- if (typeof value.locale !== "string" || !value.locale) return null;
106
104
  if (typeof value.type !== "string" || !value.type) return null;
107
105
  if (typeof value.slug !== "string" || !value.slug) return null;
108
106
  const ref: PreviewChannelRef = {
109
- locale: value.locale,
110
107
  type: value.type,
111
108
  slug: value.slug,
112
109
  };
package/src/preview.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  // packages/core/src/preview.ts
2
2
  import type { BackmatterDoc, BackmatterDocSummary } from "./doc";
3
- import { encodeDocLocale } from "./path";
4
3
 
5
4
  export type PreviewDocRef = {
6
- locale: string;
7
5
  type: string;
8
6
  slug: string;
9
7
  /** Editor checkout branch. Omit to read the workspace default branch. */
@@ -198,10 +196,9 @@ function previewParams(options: PreviewClientOptions, ref: PreviewDocRef) {
198
196
  }
199
197
 
200
198
  function docPath(options: PreviewClientOptions, ref: PreviewDocRef) {
201
- const locale = encodeURIComponent(encodeDocLocale(ref.locale));
202
199
  const type = encodeURIComponent(ref.type);
203
200
  const slug = encodeURIComponent(ref.slug);
204
- return `${baseUrl(options.apiUrl)}/preview/workspaces/${options.workspaceId}/docs/${locale}/${type}/${slug}`;
201
+ return `${baseUrl(options.apiUrl)}/preview/workspaces/${options.workspaceId}/docs/${type}/${slug}`;
205
202
  }
206
203
 
207
204
  export function previewDocRequestUrl(
@@ -223,7 +220,7 @@ export function previewTreeStreamUrl(
223
220
  options: PreviewClientOptions,
224
221
  checkout: { branch?: string; tree?: string },
225
222
  ): string {
226
- return `${baseUrl(options.apiUrl)}/preview/workspaces/${options.workspaceId}/tree/stream?${previewParams(options, { locale: "", type: "", slug: "", ...checkout })}`;
223
+ return `${baseUrl(options.apiUrl)}/preview/workspaces/${options.workspaceId}/tree/stream?${previewParams(options, { type: "", slug: "", ...checkout })}`;
227
224
  }
228
225
 
229
226
  export async function fetchPreviewDoc(
@@ -244,20 +241,18 @@ export async function fetchPreviewDoc(
244
241
  */
245
242
  export async function fetchPreviewDocs(
246
243
  options: PreviewClientOptions,
247
- query: { branch?: string; tree?: string; type?: string; locale?: string },
244
+ query: { branch?: string; tree?: string; type?: string },
248
245
  ): Promise<BackmatterDocSummary[]> {
249
246
  const docs: BackmatterDocSummary[] = [];
250
247
  let cursor: string | undefined;
251
248
  for (let page = 0; page < 100; page++) {
252
249
  const params = previewParams(options, {
253
- locale: query.locale ?? "",
254
250
  type: "",
255
251
  slug: "",
256
252
  branch: query.branch,
257
253
  tree: query.tree,
258
254
  });
259
255
  if (query.type) params.set("type", query.type);
260
- if (query.locale) params.set("locale", query.locale);
261
256
  if (cursor) params.set("cursor", cursor);
262
257
  const url = `${baseUrl(options.apiUrl)}/preview/workspaces/${options.workspaceId}/docs?${params}`;
263
258
  const res = await fetch(url, { cache: "no-store" });
package/src/query.ts CHANGED
@@ -6,8 +6,8 @@ import { normalizeFrontmatter } from "./zod";
6
6
 
7
7
  /** Loader the query layer sits on: the FS or API loaders behind createBackmatter. */
8
8
  export type DocLoader = {
9
- listDocs(filter?: { type?: string; locale?: string }): Promise<BackmatterDocSummary[]>;
10
- getDoc(locale: string, type: string, slug: string): Promise<BackmatterDoc | null>;
9
+ listDocs(filter?: { type?: string }): Promise<BackmatterDocSummary[]>;
10
+ getDoc(type: string, slug: string): Promise<BackmatterDoc | null>;
11
11
  };
12
12
 
13
13
  export type TypedDoc<M extends ModelDef> = Omit<BackmatterDoc, "type" | "frontmatter"> & {
@@ -19,7 +19,7 @@ type KeysOfKind<M extends ModelDef, K extends FieldKind> = {
19
19
  [N in keyof M["fields"]]: M["fields"][N]["kind"] extends K ? N : never;
20
20
  }[keyof M["fields"]];
21
21
 
22
- type ScalarKind = "text" | "locale" | "number" | "boolean";
22
+ type ScalarKind = "text" | "number" | "boolean";
23
23
 
24
24
  /** Equality filters on slug and scalar frontmatter fields. */
25
25
  export type Where<M extends ModelDef> = Partial<
@@ -29,7 +29,7 @@ export type Where<M extends ModelDef> = Partial<
29
29
  >
30
30
  >;
31
31
 
32
- /** Relation fields that may be resolved (depth 1, same locale). */
32
+ /** Relation fields that may be resolved (depth 1). */
33
33
  export type Include<M extends ModelDef> = Partial<Record<KeysOfKind<M, "relation">, true>>;
34
34
 
35
35
  type RelationTarget<
@@ -50,22 +50,13 @@ export type Resolved<
50
50
  : never;
51
51
  };
52
52
 
53
- type PathHasLocale<M extends ModelDef> = M["path"] extends `${string}{locale}${string}`
54
- ? true
55
- : false;
56
-
57
53
  type ModelGet<M extends ModelDef> = M["singleton"] extends true
58
- ? PathHasLocale<M> extends true
59
- ? (locale: string) => Promise<TypedDoc<M> | null>
60
- : () => Promise<TypedDoc<M> | null>
61
- : PathHasLocale<M> extends true
62
- ? (locale: string, slug: string) => Promise<TypedDoc<M> | null>
63
- : (slug: string) => Promise<TypedDoc<M> | null>;
54
+ ? () => Promise<TypedDoc<M> | null>
55
+ : (slug: string) => Promise<TypedDoc<M> | null>;
64
56
 
65
57
  export type ModelQuery<M extends ModelDef, Models extends readonly ModelDef[]> = {
66
58
  get: ModelGet<M>;
67
59
  findMany<I extends Include<M> = {}>(opts?: {
68
- locale?: string;
69
60
  where?: Where<M>;
70
61
  include?: I;
71
62
  }): Promise<Array<TypedDoc<M> & Resolved<M, Models, I>>>;
@@ -76,7 +67,6 @@ export type Query<Models extends readonly ModelDef[]> = {
76
67
  };
77
68
 
78
69
  type RawOpts = {
79
- locale?: string;
80
70
  where?: Record<string, unknown>;
81
71
  include?: Record<string, boolean | undefined>;
82
72
  };
@@ -87,24 +77,17 @@ export function createQuery<const Models extends readonly ModelDef[]>(
87
77
  ): Query<Models> {
88
78
  const byType = new Map<string, ModelDef>(models.map((m) => [m.type, m]));
89
79
 
90
- function identityFor(type: string, a?: string, b?: string): { locale: string; slug: string } {
80
+ function slugForGet(type: string, arg?: string): string {
91
81
  const model = byType.get(type);
92
- const localized = model?.path.includes("{locale}") ?? true;
93
82
  const singleton = model?.singleton === true;
94
83
  if (singleton) {
95
- return {
96
- locale: localized ? (a ?? "") : "",
97
- slug: model?.path.includes("{slug}") ? (localized ? (b ?? IMPLICIT_SINGLETON_SLUG) : (a ?? IMPLICIT_SINGLETON_SLUG)) : IMPLICIT_SINGLETON_SLUG,
98
- };
84
+ return model?.path.includes("{slug}") ? (arg ?? IMPLICIT_SINGLETON_SLUG) : IMPLICIT_SINGLETON_SLUG;
99
85
  }
100
- return {
101
- locale: localized ? (a ?? "") : "",
102
- slug: localized ? (b ?? "") : (a ?? ""),
103
- };
86
+ return arg ?? "";
104
87
  }
105
88
 
106
- async function get(type: string, locale: string, slug: string): Promise<BackmatterDoc | null> {
107
- const doc = await loader.getDoc(locale, type, slug);
89
+ async function get(type: string, slug: string): Promise<BackmatterDoc | null> {
90
+ const doc = await loader.getDoc(type, slug);
108
91
  if (!doc) return null;
109
92
  return { ...doc, frontmatter: normalizeFrontmatter(doc.frontmatter) };
110
93
  }
@@ -123,20 +106,16 @@ export function createQuery<const Models extends readonly ModelDef[]>(
123
106
  const whereSlug = opts?.where?.slug;
124
107
 
125
108
  let docs: BackmatterDoc[];
126
- // Truthy gate: the loaders' locale filter is truthiness-based, so a falsy
127
- // locale ("") must fall back to the list path like an omitted one.
128
- if (typeof whereSlug === "string" && opts?.locale) {
129
- // Slug fast-path: identity lookup instead of list + load fan-out.
130
- const doc = await get(type, opts.locale, whereSlug);
109
+ if (typeof whereSlug === "string") {
110
+ const doc = await get(type, whereSlug);
131
111
  docs = doc ? [doc] : [];
132
112
  } else {
133
- const summaries = await loader.listDocs({ type, locale: opts?.locale });
113
+ const summaries = await loader.listDocs({ type });
134
114
  const fromList = summaries.filter((s) => s.frontmatter != null);
135
115
  const missing = summaries.filter((s) => s.frontmatter == null);
136
- const loaded = await Promise.all(missing.map((s) => get(type, s.locale, s.slug)));
116
+ const loaded = await Promise.all(missing.map((s) => get(type, s.slug)));
137
117
  docs = [
138
118
  ...fromList.map((s) => ({
139
- locale: s.locale,
140
119
  type: s.type,
141
120
  slug: s.slug,
142
121
  source: "head" as const,
@@ -152,13 +131,12 @@ export function createQuery<const Models extends readonly ModelDef[]>(
152
131
  const include = opts?.include;
153
132
  if (!include || !model) return filtered;
154
133
 
155
- // Memoize relation loads per call: N posts sharing one author load it once.
156
134
  const relationCache = new Map<string, Promise<BackmatterDoc | null>>();
157
- const getCached = (target: string, locale: string, slug: string) => {
158
- const key = `${target}:${locale}:${slug}`;
135
+ const getCached = (target: string, slug: string) => {
136
+ const key = `${target}:${slug}`;
159
137
  let promise = relationCache.get(key);
160
138
  if (!promise) {
161
- promise = get(target, locale, slug);
139
+ promise = get(target, slug);
162
140
  relationCache.set(key, promise);
163
141
  }
164
142
  return promise;
@@ -171,10 +149,10 @@ export function createQuery<const Models extends readonly ModelDef[]>(
171
149
  if (!on) continue;
172
150
  const field = model.fields[name];
173
151
  const target = field?.kind === "relation" ? field.relationTo : undefined;
174
- const slug = doc.frontmatter[name];
152
+ const relatedSlug = doc.frontmatter[name];
175
153
  extra[name] =
176
- target && typeof slug === "string" && slug.length > 0
177
- ? await getCached(target, doc.locale, slug)
154
+ target && typeof relatedSlug === "string" && relatedSlug.length > 0
155
+ ? await getCached(target, relatedSlug)
178
156
  : null;
179
157
  }
180
158
  return { ...doc, ...extra };
@@ -185,10 +163,7 @@ export function createQuery<const Models extends readonly ModelDef[]>(
185
163
  const query: Record<string, unknown> = {};
186
164
  for (const m of models) {
187
165
  query[m.type] = {
188
- get: (a?: string, b?: string) => {
189
- const id = identityFor(m.type, a, b);
190
- return get(m.type, id.locale, id.slug);
191
- },
166
+ get: (arg?: string) => get(m.type, slugForGet(m.type, arg)),
192
167
  findMany: (opts?: RawOpts) => findMany(m.type, opts),
193
168
  };
194
169
  }
package/src/routes.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Pure URL ↔ document mapping for the host catch-all route. No framework code.
3
3
  import type { ModelSnapshot } from "./model";
4
4
 
5
- export const DEFAULT_ROUTE = "/{locale}/{type}/{slug}";
5
+ export const DEFAULT_ROUTE = "/{type}/{slug}";
6
6
 
7
7
  export type RouteModel = Pick<ModelSnapshot, "type" | "route" | "indexSlug">;
8
8
 
@@ -12,35 +12,30 @@ export function isRoutable(model: Pick<RouteModel, "route">): boolean {
12
12
 
13
13
  export type RouteMatch = {
14
14
  type: string;
15
- locale: string;
16
15
  slug: string;
17
16
  };
18
17
 
19
- export type MatchRouteOptions = {
20
- /** Locale for routes without a `{locale}` placeholder. */
21
- defaultLocale: string;
22
- /** When set, `{locale}` only matches one of these values. */
23
- locales?: readonly string[];
24
- };
25
-
26
18
  /** The model's route template with `{type}` already substituted. */
27
19
  export function routeTemplate(model: RouteModel): string {
28
20
  if (model.route === false) return "";
29
- return (model.route ?? DEFAULT_ROUTE).replaceAll("{type}", model.type);
21
+ const template = model.route ?? DEFAULT_ROUTE;
22
+ if (template.includes("{locale}")) {
23
+ throw new Error("model.route must not contain {locale}");
24
+ }
25
+ return template.replaceAll("{type}", model.type);
30
26
  }
31
27
 
32
- /** Build the public URL path for a document, e.g. "/en/blog/hello". */
33
- export function routeFor(model: RouteModel, locale: string, slug: string): string {
28
+ /** Build the public URL path for a document, e.g. "/blog/hello". */
29
+ export function routeFor(model: RouteModel, slug: string): string {
34
30
  if (model.route === false) return "";
35
31
  if (model.indexSlug && slug === model.indexSlug) return "/";
36
32
  return routeTemplate(model)
37
- .replaceAll("{locale}", encodeURIComponent(locale))
38
33
  .replaceAll("{slug}", slug.split("/").map(encodeURIComponent).join("/"));
39
34
  }
40
35
 
41
- /** URL segments for Next `generateStaticParams` (["en", "blog", "hello"]). */
42
- export function routeSegments(model: RouteModel, locale: string, slug: string): string[] {
43
- return routeFor(model, locale, slug)
36
+ /** URL segments for Next `generateStaticParams` (["blog", "hello"]). */
37
+ export function routeSegments(model: RouteModel, slug: string): string[] {
38
+ return routeFor(model, slug)
44
39
  .split("/")
45
40
  .filter(Boolean)
46
41
  .map(decodeURIComponent);
@@ -51,11 +46,13 @@ function escapeRegex(s: string): string {
51
46
  }
52
47
 
53
48
  export function compileRoutePattern(template: string): RegExp {
54
- const parts = template.split(/(\{locale\}|\{slug\})/g);
49
+ if (template.includes("{locale}")) {
50
+ throw new Error("model.route must not contain {locale}");
51
+ }
52
+ const parts = template.split(/(\{slug\})/g);
55
53
  let pattern = "^";
56
54
  for (const part of parts) {
57
- if (part === "{locale}") pattern += "(?<locale>[^/]+)";
58
- else if (part === "{slug}") pattern += "(?<slug>[^/]+)";
55
+ if (part === "{slug}") pattern += "(?<slug>[^/]+)";
59
56
  else pattern += escapeRegex(part);
60
57
  }
61
58
  return new RegExp(`${pattern}/?$`);
@@ -89,13 +86,12 @@ export function normalizePathname(pathname: string): string {
89
86
  export function matchRoute(
90
87
  models: readonly RouteModel[],
91
88
  pathname: string,
92
- options: MatchRouteOptions,
93
89
  ): RouteMatch | null {
94
90
  const target = normalizePathname(pathname);
95
91
  if (target === "/") {
96
92
  const index = models.find((model) => model.indexSlug && isRoutable(model));
97
93
  if (index?.indexSlug) {
98
- return { type: index.type, locale: options.defaultLocale, slug: index.indexSlug };
94
+ return { type: index.type, slug: index.indexSlug };
99
95
  }
100
96
  return null;
101
97
  }
@@ -103,14 +99,7 @@ export function matchRoute(
103
99
  const template = routeTemplate(model);
104
100
  const m = compileRoutePattern(template).exec(target);
105
101
  if (!m?.groups?.slug) continue;
106
- let locale = options.defaultLocale;
107
- if (template.includes("{locale}")) {
108
- const raw = m.groups.locale;
109
- if (!raw) continue;
110
- locale = decodeURIComponent(raw);
111
- if (options.locales && !options.locales.includes(locale)) continue;
112
- }
113
- return { type: model.type, locale, slug: decodeURIComponent(m.groups.slug) };
102
+ return { type: model.type, slug: decodeURIComponent(m.groups.slug) };
114
103
  }
115
104
  return null;
116
105
  }
package/src/source.ts CHANGED
@@ -2,15 +2,15 @@
2
2
  // A source resolves the outside record behind an `external` model's route.
3
3
  // Host code only (`sources/{type}.ts`); never serialized into the schema.
4
4
 
5
- export type SourceRef = { locale: string; slug: string };
5
+ export type SourceRef = { slug: string };
6
6
 
7
7
  export type SourceDef<T = unknown> = {
8
8
  /** The outside record for a route hit. `null` means the record does not exist (404). */
9
9
  load: (ref: SourceRef) => Promise<T | null>;
10
- /** Slugs to prerender, per locale. Omit to prerender only slugs that have a CMS file. */
11
- list?: (locale: string) => Promise<readonly string[]>;
10
+ /** Slugs to prerender. Omit to prerender only slugs that have a CMS file. */
11
+ list?: () => Promise<readonly string[]>;
12
12
  /** One representative record for template preview. Omit to preview the template with `data: null`. */
13
- sample?: (locale: string) => Promise<T | null>;
13
+ sample?: () => Promise<T | null>;
14
14
  /** Metadata when the document has no `title`. */
15
15
  metadata?: (record: T) => { title?: string };
16
16
  };
@@ -38,7 +38,11 @@ export async function syncSchema(options: SyncSchemaOptions): Promise<void> {
38
38
  options.objects,
39
39
  options.richtext,
40
40
  );
41
- const body: { payload: ReturnType<typeof serializeSchema>; branch: string; siteUrl?: string } = {
41
+ const body: {
42
+ payload: ReturnType<typeof serializeSchema>;
43
+ branch: string;
44
+ siteUrl?: string;
45
+ } = {
42
46
  payload,
43
47
  branch,
44
48
  };