@digital-gravy/etch-public-api 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -283,6 +283,34 @@ if (etch.environment?.capabilities.dataSources === true) {
283
283
  Reads (`list`, `get`, `findDataSource`) are synchronous; writes persist to the
284
284
  backend immediately and do not wait for `saveAsync()`.
285
285
 
286
+ ### Routes
287
+
288
+ `etch.routes` is the site's route tree: segments hanging from the site root,
289
+ each serving content records through a template. The namespace is **optional** — a runtime
290
+ without the `routes` capability omits it.
291
+
292
+ ```ts
293
+ if (etch.environment?.capabilities.routes === true) {
294
+ const routes = etch.routes!;
295
+
296
+ // A template is a record of a template content type
297
+ const [template] = await etch.navigation.listTemplatesAsync();
298
+
299
+ const blog = await routes.createAsync({ segment: 'blog' });
300
+ await routes.createAsync({
301
+ parentId: blog.id,
302
+ segment: '{this.title.toSlug()}',
303
+ source: { postTypes: ['post'] },
304
+ templateId: template.id
305
+ });
306
+
307
+ // Where every record lands, as the build would publish it
308
+ const paths = await routes.getPathsAsync();
309
+ }
310
+ ```
311
+
312
+ Writes persist to the backend immediately and do not wait for `saveAsync()`.
313
+
286
314
  ### Skills
287
315
 
288
316
  `etch.skills` exposes the bundled, read-only authoring guides Etch ships (e.g.
@@ -347,6 +375,7 @@ is imported.
347
375
  - **Components** — `EtchComponentsApi`, `PublicComponentSummary`, `PublicComponentJson`, `ComponentPatch`, `ComponentProperty`, …
348
376
  - **Loops** — `EtchLoopsApi`, `EtchLoop`, `EtchLoopConfig`, `BlockLoopBinding`, …
349
377
  - **Data sources** — `EtchDataSourcesApi`, `EtchDataSource`, `EtchDataSourceType`, `EtchDataSourcePatch`
378
+ - **Routes** — `EtchRoutesApi`, `EtchRoute`, `EtchRouteInput`, `EtchRoutePatch`, `EtchRouteSource`, `EtchRoutedPath`
350
379
  - **Navigation** — `EtchNavigationApi`, `NavigationPlace`, `PostSummary`, `TemplateSummary`
351
380
  - **Fields** — `EtchFieldsApi`, `CustomField`, `CustomFieldGroup`, …
352
381
  - **Skills** — `EtchSkillsApi`, `SkillSummary`, `SkillDetail`
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";;;AA0BO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAGvC,WAAA,CAAY,MAAwB,OAAA,EAAiB;AACpD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACb;AACD;AAGO,SAAS,eAAe,KAAA,EAAuC;AACrE,EAAA,OACC,KAAA,YAAiB,YAAA,IAChB,KAAA,EAAiB,IAAA,KAAS,cAAA;AAE7B;;;AClCO,IAAM,gBAAA,GAAmB;;;ACIhC,SAAS,QAAA,GAA6B;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,OAAO,KAAA,CAAM,IAAA;AACd;AAMO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,UAAS,KAAM,MAAA;AACvB;AAGA,SAAS,QAAQ,OAAA,EAAgC;AAChD,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,IAAA;AACnC;AAOA,SAAS,kBAAA,CAAmB,WAAmB,OAAA,EAAuB;AACrE,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAS,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AACrD,EAAA,OAAA,CAAQ,IAAA;AAAA,IACP,CAAA,qDAAA,EAAwD,SAAS,CAAA,wBAAA,EAC9C,OAAO,CAAA,sBAAA;AAAA,GAC3B;AACD;AAsBO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EAAS;AAC3D,EAAA,MAAM,OAAO,QAAA,EAAS;AACtB,EAAA,IAAI,CAAC,IAAA,EAAM;AACV,IAAA,MAAM,IAAI,YAAA;AAAA,MACT,eAAA;AAAA,MACA;AAAA,KAED;AAAA,EACD;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,KAAY,UAAA,EAAY;AACvC,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,QAAQ,UAAA,EAAY;AACvB,IAAA,kBAAA;AAAA,MACC,OAAA,CAAQ,UAAA;AAAA,MACR,KAAK,UAAA,IAAc;AAAA,KACpB;AAAA,EACD;AACA,EAAA,OAAO,IAAA;AACR","file":"index.cjs","sourcesContent":["/**\n * Error codes thrown by the public `window.etch` API.\n *\n * The API throws typed errors (rather than returning sentinels) so that AI\n * assistants and plugin authors can `try`/`catch` and react to a precise cause.\n *\n * The union ends with `(string & {})` so that codes added by newer Etch\n * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for\n * the known values.\n */\nexport type EtchApiErrorCode =\n\t| 'BLOCK_NOT_FOUND'\n\t| 'WRONG_BLOCK_TYPE'\n\t| 'READONLY'\n\t| 'INVALID_ARGUMENT'\n\t| 'LOOP_NOT_FOUND'\n\t| 'DATA_SOURCE_NOT_FOUND'\n\t| 'STYLE_NOT_FOUND'\n\t| 'STYLESHEET_NOT_FOUND'\n\t| 'COMPONENT_NOT_FOUND'\n\t| 'POST_NOT_FOUND'\n\t| 'OPERATION_FAILED'\n\t| 'NOT_AVAILABLE'\n\t| (string & {});\n\n/** Error thrown by the public Etch API (and by this client). */\nexport class EtchApiError extends Error {\n\treadonly code: EtchApiErrorCode;\n\n\tconstructor(code: EtchApiErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EtchApiError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Narrow an unknown caught value to an {@link EtchApiError}. */\nexport function isEtchApiError(value: unknown): value is EtchApiError {\n\treturn (\n\t\tvalue instanceof EtchApiError ||\n\t\t(value as Error)?.name === 'EtchApiError'\n\t);\n}\n","/**\n * Version of the Etch scripting **contract** this package targets, independent\n * of the Etch product version and of this package's own npm version.\n *\n * `0.x` signals the surface is **experimental** and may change without a major\n * bump until it stabilizes. It matches the value returned by the runtime's\n * {@link Etch.apiVersion} getter on `window.etch`.\n */\nexport const ETCH_API_VERSION = '0.x';\n","import type { ConnectOptions, Etch } from './contract';\nimport { EtchApiError } from './errors';\nimport { ETCH_API_VERSION } from './version';\n\ndeclare global {\n\tinterface Window {\n\t\t/** The Etch scripting API, present once the builder has loaded. */\n\t\tetch?: Etch;\n\t}\n}\n\n/** Read `window.etch` from whatever global object exists, or `undefined`. */\nfunction readEtch(): Etch | undefined {\n\tconst scope = globalThis as { etch?: Etch };\n\treturn scope.etch;\n}\n\n/**\n * Whether the Etch scripting API is present on the page. Use this to guard code\n * that should no-op when not running inside the builder.\n */\nexport function isEtchAvailable(): boolean {\n\treturn readEtch() !== undefined;\n}\n\n/** The major version number of a semver-ish string, or `null` if unparseable. */\nfunction majorOf(version: string): number | null {\n\tconst match = /^\\D*(\\d+)/.exec(version);\n\treturn match ? Number(match[1]) : null;\n}\n\n/**\n * Best-effort compatibility check for `0.x` runtimes that have no native\n * `connect()`. Warns (does not throw) when the requested major differs from the\n * runtime's, so experimental consumers aren't hard-blocked.\n */\nfunction warnIfIncompatible(requested: string, runtime: string): void {\n\tconst want = majorOf(requested);\n\tconst have = majorOf(runtime);\n\tif (want === null || have === null || want === have) return;\n\tconsole.warn(\n\t\t`[@digital-gravy/etch-public-api] Requested Etch API v${requested} but the ` +\n\t\t\t`page provides v${runtime}. Behavior may differ.`\n\t);\n}\n\n/**\n * Acquire the Etch scripting API from the page.\n *\n * The runtime lives on `window.etch` (injected by the builder); this returns it\n * typed. When the page exposes a future stable runtime with a native\n * `connect()`, version negotiation is delegated to it. On today's `0.x`\n * runtime, the global is returned directly after a best-effort version check.\n *\n * @throws {EtchApiError} `NOT_AVAILABLE` when the builder is not present.\n *\n * @example\n * ```ts\n * import { getEtch } from '@etchwp/public-api';\n *\n * const etch = getEtch();\n * const ids = etch.blocks.find({ type: 'text' });\n * etch.blocks.setText(ids[0], 'Hello');\n * await etch.saveAsync();\n * ```\n */\nexport function getEtch(options: ConnectOptions = {}): Etch {\n\tconst etch = readEtch();\n\tif (!etch) {\n\t\tthrow new EtchApiError(\n\t\t\t'NOT_AVAILABLE',\n\t\t\t'window.etch is not available. The Etch builder is not loaded on this page, ' +\n\t\t\t\t'or getEtch() ran before it finished initializing.'\n\t\t);\n\t}\n\n\tif (typeof etch.connect === 'function') {\n\t\treturn etch.connect(options);\n\t}\n\n\tif (options.apiVersion) {\n\t\twarnIfIncompatible(\n\t\t\toptions.apiVersion,\n\t\t\tetch.apiVersion ?? ETCH_API_VERSION\n\t\t);\n\t}\n\treturn etch;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";;;AA2BO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAGvC,WAAA,CAAY,MAAwB,OAAA,EAAiB;AACpD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACb;AACD;AAGO,SAAS,eAAe,KAAA,EAAuC;AACrE,EAAA,OACC,KAAA,YAAiB,YAAA,IAChB,KAAA,EAAiB,IAAA,KAAS,cAAA;AAE7B;;;ACnCO,IAAM,gBAAA,GAAmB;;;ACIhC,SAAS,QAAA,GAA6B;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,OAAO,KAAA,CAAM,IAAA;AACd;AAMO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,UAAS,KAAM,MAAA;AACvB;AAGA,SAAS,QAAQ,OAAA,EAAgC;AAChD,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,IAAA;AACnC;AAOA,SAAS,kBAAA,CAAmB,WAAmB,OAAA,EAAuB;AACrE,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAS,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AACrD,EAAA,OAAA,CAAQ,IAAA;AAAA,IACP,CAAA,qDAAA,EAAwD,SAAS,CAAA,wBAAA,EAC9C,OAAO,CAAA,sBAAA;AAAA,GAC3B;AACD;AAsBO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EAAS;AAC3D,EAAA,MAAM,OAAO,QAAA,EAAS;AACtB,EAAA,IAAI,CAAC,IAAA,EAAM;AACV,IAAA,MAAM,IAAI,YAAA;AAAA,MACT,eAAA;AAAA,MACA;AAAA,KAED;AAAA,EACD;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,KAAY,UAAA,EAAY;AACvC,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,QAAQ,UAAA,EAAY;AACvB,IAAA,kBAAA;AAAA,MACC,OAAA,CAAQ,UAAA;AAAA,MACR,KAAK,UAAA,IAAc;AAAA,KACpB;AAAA,EACD;AACA,EAAA,OAAO,IAAA;AACR","file":"index.cjs","sourcesContent":["/**\n * Error codes thrown by the public `window.etch` API.\n *\n * The API throws typed errors (rather than returning sentinels) so that AI\n * assistants and plugin authors can `try`/`catch` and react to a precise cause.\n *\n * The union ends with `(string & {})` so that codes added by newer Etch\n * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for\n * the known values.\n */\nexport type EtchApiErrorCode =\n\t| 'BLOCK_NOT_FOUND'\n\t| 'WRONG_BLOCK_TYPE'\n\t| 'READONLY'\n\t| 'INVALID_ARGUMENT'\n\t| 'LOOP_NOT_FOUND'\n\t| 'DATA_SOURCE_NOT_FOUND'\n\t| 'STYLE_NOT_FOUND'\n\t| 'STYLESHEET_NOT_FOUND'\n\t| 'COMPONENT_NOT_FOUND'\n\t| 'POST_NOT_FOUND'\n\t| 'ROUTE_NOT_FOUND'\n\t| 'OPERATION_FAILED'\n\t| 'NOT_AVAILABLE'\n\t| (string & {});\n\n/** Error thrown by the public Etch API (and by this client). */\nexport class EtchApiError extends Error {\n\treadonly code: EtchApiErrorCode;\n\n\tconstructor(code: EtchApiErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EtchApiError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Narrow an unknown caught value to an {@link EtchApiError}. */\nexport function isEtchApiError(value: unknown): value is EtchApiError {\n\treturn (\n\t\tvalue instanceof EtchApiError ||\n\t\t(value as Error)?.name === 'EtchApiError'\n\t);\n}\n","/**\n * Version of the Etch scripting **contract** this package targets, independent\n * of the Etch product version and of this package's own npm version.\n *\n * `0.x` signals the surface is **experimental** and may change without a major\n * bump until it stabilizes. It matches the value returned by the runtime's\n * {@link Etch.apiVersion} getter on `window.etch`.\n */\nexport const ETCH_API_VERSION = '0.x';\n","import type { ConnectOptions, Etch } from './contract';\nimport { EtchApiError } from './errors';\nimport { ETCH_API_VERSION } from './version';\n\ndeclare global {\n\tinterface Window {\n\t\t/** The Etch scripting API, present once the builder has loaded. */\n\t\tetch?: Etch;\n\t}\n}\n\n/** Read `window.etch` from whatever global object exists, or `undefined`. */\nfunction readEtch(): Etch | undefined {\n\tconst scope = globalThis as { etch?: Etch };\n\treturn scope.etch;\n}\n\n/**\n * Whether the Etch scripting API is present on the page. Use this to guard code\n * that should no-op when not running inside the builder.\n */\nexport function isEtchAvailable(): boolean {\n\treturn readEtch() !== undefined;\n}\n\n/** The major version number of a semver-ish string, or `null` if unparseable. */\nfunction majorOf(version: string): number | null {\n\tconst match = /^\\D*(\\d+)/.exec(version);\n\treturn match ? Number(match[1]) : null;\n}\n\n/**\n * Best-effort compatibility check for `0.x` runtimes that have no native\n * `connect()`. Warns (does not throw) when the requested major differs from the\n * runtime's, so experimental consumers aren't hard-blocked.\n */\nfunction warnIfIncompatible(requested: string, runtime: string): void {\n\tconst want = majorOf(requested);\n\tconst have = majorOf(runtime);\n\tif (want === null || have === null || want === have) return;\n\tconsole.warn(\n\t\t`[@digital-gravy/etch-public-api] Requested Etch API v${requested} but the ` +\n\t\t\t`page provides v${runtime}. Behavior may differ.`\n\t);\n}\n\n/**\n * Acquire the Etch scripting API from the page.\n *\n * The runtime lives on `window.etch` (injected by the builder); this returns it\n * typed. When the page exposes a future stable runtime with a native\n * `connect()`, version negotiation is delegated to it. On today's `0.x`\n * runtime, the global is returned directly after a best-effort version check.\n *\n * @throws {EtchApiError} `NOT_AVAILABLE` when the builder is not present.\n *\n * @example\n * ```ts\n * import { getEtch } from '@etchwp/public-api';\n *\n * const etch = getEtch();\n * const ids = etch.blocks.find({ type: 'text' });\n * etch.blocks.setText(ids[0], 'Hello');\n * await etch.saveAsync();\n * ```\n */\nexport function getEtch(options: ConnectOptions = {}): Etch {\n\tconst etch = readEtch();\n\tif (!etch) {\n\t\tthrow new EtchApiError(\n\t\t\t'NOT_AVAILABLE',\n\t\t\t'window.etch is not available. The Etch builder is not loaded on this page, ' +\n\t\t\t\t'or getEtch() ran before it finished initializing.'\n\t\t);\n\t}\n\n\tif (typeof etch.connect === 'function') {\n\t\treturn etch.connect(options);\n\t}\n\n\tif (options.apiVersion) {\n\t\twarnIfIncompatible(\n\t\t\toptions.apiVersion,\n\t\t\tetch.apiVersion ?? ETCH_API_VERSION\n\t\t);\n\t}\n\treturn etch;\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -717,12 +717,16 @@ type EtchProduct = 'etch-wp' | 'etch-studio' | (string & {});
717
717
  *
718
718
  * - `fields` — `etch.fields.*` is backed by a real field store.
719
719
  * - `templates` — template posts exist and `navigation.goTo('templates')` works.
720
+ * On Studio a template post is a record of a template content type, and
721
+ * `goTo('templates')` opens the content hub, where they live.
722
+ * - `routes` — `etch.routes.*` is backed: the site's URLs are a route tree the
723
+ * author draws, and each route picks a template for the records it serves.
720
724
  * - `wpMedia` — media ids resolve against the **WordPress media library**, so a
721
725
  * URL can be turned into an id through `/wp/v2/media`. Without it the ids are
722
726
  * the product's own asset store instead. Either way an `etch/dynamic-image`
723
727
  * block binds the same: a numeric `mediaId` attribute.
724
728
  */
725
- type EtchCapability = 'loops' | 'dataSources' | 'fields' | 'templates' | 'wpMedia';
729
+ type EtchCapability = 'loops' | 'dataSources' | 'fields' | 'templates' | 'wpMedia' | 'routes';
726
730
  /**
727
731
  * Capability map. Every key is optional on purpose: a runtime older than a
728
732
  * capability's introduction simply omits it, so **absent means unavailable**.
@@ -1246,9 +1250,11 @@ interface EtchComponentsApi {
1246
1250
  * - `content-hub` — the pages/posts browser
1247
1251
  * - `style-manager` — the global style manager
1248
1252
  * - `loop-manager` — the loop manager
1253
+ * - `data-manager` — the data source manager
1249
1254
  * - `asset-manager` — the asset (media) library
1255
+ * - `routes` — the routes manager
1250
1256
  */
1251
- type NavigationPlace = 'builder' | 'templates' | 'style-manager' | 'content-hub' | 'loop-manager' | 'data-manager' | 'asset-manager';
1257
+ type NavigationPlace = 'builder' | 'templates' | 'style-manager' | 'content-hub' | 'loop-manager' | 'data-manager' | 'asset-manager' | 'routes';
1252
1258
  /** Lightweight post entry returned by `navigation.listPostsAsync()`. */
1253
1259
  interface PostSummary {
1254
1260
  /** Post id. */
@@ -1293,6 +1299,126 @@ interface EtchNavigationApi {
1293
1299
  listTemplatesAsync(): Promise<TemplateSummary[]>;
1294
1300
  }
1295
1301
 
1302
+ /**
1303
+ * The site's route tree and the `etch.routes` API surface.
1304
+ *
1305
+ * Routes are a tree of segments hanging from the site root; a route's URL is
1306
+ * its ancestors' segments joined by `/`. What a segment is depends on how it is
1307
+ * written:
1308
+ *
1309
+ * - `''` — the site root. Exactly one, never created or deleted.
1310
+ * - `about` — static: lowercase letters and digits joined by `-` or `_`.
1311
+ * Serves at most one record.
1312
+ * - `(marketing)` — a group: nests routes without adding to the URL. Serves
1313
+ * nothing itself.
1314
+ * - `{this.title.toSlug()}` — dynamic: resolved once per record it serves,
1315
+ * from that record's fields. Serves any mix of records and whole content
1316
+ * types. `this.slug` and `this.isHomepage` cannot be used — the routes
1317
+ * decide them.
1318
+ *
1319
+ * Records are what `navigation.listPostsAsync()` lists, and their content type
1320
+ * is its `postType` — so the fields below keep the `post` names. Templates are
1321
+ * records of a template content type (`navigation.listTemplatesAsync()`). A
1322
+ * route renders the records it serves through its template, or the nearest
1323
+ * ancestor's when it has none.
1324
+ *
1325
+ * Check `etch.environment.capabilities.routes === true` before using this
1326
+ * namespace — a runtime without it omits the whole thing.
1327
+ */
1328
+ /** What a route is, read off its segment. */
1329
+ type EtchRouteSegmentKind = 'root' | 'static' | 'group' | 'dynamic';
1330
+ /** What a route serves: these records, plus every record of these content types. */
1331
+ interface EtchRouteSource {
1332
+ /** Record ids, as `navigation.listPostsAsync()` returns them. */
1333
+ postIds: number[];
1334
+ /** Content type keys — `PostSummary.postType`. Dynamic routes only. */
1335
+ postTypes: string[];
1336
+ }
1337
+ /** One node of the route tree. */
1338
+ interface EtchRoute {
1339
+ /** Opaque string id. */
1340
+ id: string;
1341
+ /** The route this one sits inside. Absent only on the site root. */
1342
+ parentId?: string;
1343
+ /** This route's own part of the URL — see the module docs for the grammar. */
1344
+ segment: string;
1345
+ kind: EtchRouteSegmentKind;
1346
+ source: EtchRouteSource;
1347
+ /** Id of the template record. Absent means inherited from the nearest ancestor. */
1348
+ templateId?: number;
1349
+ }
1350
+ /** A route to create with `routes.createAsync()`. */
1351
+ interface EtchRouteInput {
1352
+ segment: string;
1353
+ /** Defaults to the site root. */
1354
+ parentId?: string;
1355
+ /** Defaults to serving nothing; a missing list is empty. */
1356
+ source?: Partial<EtchRouteSource>;
1357
+ templateId?: number;
1358
+ }
1359
+ /** Fields of an {@link EtchRoute} that `routes.updateAsync()` can change. */
1360
+ interface EtchRoutePatch {
1361
+ /** Renames the route; everything beneath it moves with it. */
1362
+ segment?: string;
1363
+ /** Moves the route (and its subtree) inside another route. */
1364
+ parentId?: string;
1365
+ /** Replaces what the route serves, whole. A missing list is empty. */
1366
+ source?: Partial<EtchRouteSource>;
1367
+ /** `null` clears the template, so the route inherits again. */
1368
+ templateId?: number | null;
1369
+ }
1370
+ /**
1371
+ * A URL the tree resolves to, and what the build does with it: publishes a
1372
+ * record there, loses it to a more specific route (`shadowed`), has two records
1373
+ * fighting for it (`conflict`), or refuses the segment for a record (`invalid`).
1374
+ */
1375
+ interface EtchRoutedPath {
1376
+ routeId: string;
1377
+ /** The record served there. Absent for a static route that only holds its path. */
1378
+ postId?: number;
1379
+ /** The record's title. */
1380
+ title?: string;
1381
+ /** The URL path; the route as written when `status` is `invalid`. */
1382
+ path: string;
1383
+ status: 'published' | 'shadowed' | 'conflict' | 'invalid';
1384
+ /** Why it is not published. */
1385
+ reason?: string;
1386
+ }
1387
+ /**
1388
+ * The project's routes. Writes persist to the backend immediately — they do
1389
+ * not wait for {@link Etch.saveAsync}.
1390
+ */
1391
+ interface EtchRoutesApi {
1392
+ /** Every route, the site root included. */
1393
+ listAsync(): Promise<EtchRoute[]>;
1394
+ /**
1395
+ * Every URL the tree resolves to, as the build would publish it. Read this
1396
+ * after a change to see where records actually land.
1397
+ */
1398
+ getPathsAsync(): Promise<EtchRoutedPath[]>;
1399
+ /**
1400
+ * Add a route.
1401
+ * @throws {EtchApiError} `INVALID_ARGUMENT` on a malformed segment, a source
1402
+ * its kind forbids, a name a sibling already has, an unknown content type, or
1403
+ * a template that is not a template record.
1404
+ * @throws {EtchApiError} `ROUTE_NOT_FOUND` when `parentId` names no route.
1405
+ */
1406
+ createAsync(route: EtchRouteInput): Promise<EtchRoute>;
1407
+ /**
1408
+ * Change a route. Only the fields present are applied.
1409
+ * @throws {EtchApiError} `ROUTE_NOT_FOUND`
1410
+ * @throws {EtchApiError} `INVALID_ARGUMENT` — as `createAsync`, or a move
1411
+ * that would put a route inside itself.
1412
+ */
1413
+ updateAsync(routeId: string, patch: EtchRoutePatch): Promise<EtchRoute>;
1414
+ /**
1415
+ * Delete a route and every route beneath it.
1416
+ * @throws {EtchApiError} `ROUTE_NOT_FOUND`
1417
+ * @throws {EtchApiError} `INVALID_ARGUMENT` for the site root.
1418
+ */
1419
+ deleteAsync(routeId: string): Promise<void>;
1420
+ }
1421
+
1296
1422
  /**
1297
1423
  * Custom field group/value shapes and the `etch.fields` API surface.
1298
1424
  */
@@ -1651,6 +1777,14 @@ interface Etch {
1651
1777
  * property) before calling it.
1652
1778
  */
1653
1779
  dataSources?: EtchDataSourcesApi;
1780
+ /**
1781
+ * The site's route tree, and which template renders each route's posts.
1782
+ *
1783
+ * Optional: a runtime that does not back the `routes` capability omits the
1784
+ * namespace, so check `environment.capabilities.routes === true` (or the
1785
+ * presence of this property) before calling it.
1786
+ */
1787
+ routes?: EtchRoutesApi;
1654
1788
  /** Global style (CSS) definitions and CSS variables. */
1655
1789
  styles: EtchStylesApi;
1656
1790
  /** Global stylesheets and `@custom-media` definitions. */
@@ -1702,7 +1836,7 @@ interface Etch {
1702
1836
  readonly version: string;
1703
1837
  /**
1704
1838
  * What this runtime is and which optional surfaces it backs. Read this
1705
- * before using `loops`, `fields`, templates or WordPress media, and before
1839
+ * before using `loops`, `fields`, `routes`, templates or WordPress media, and before
1706
1840
  * authoring a block type that may not exist here.
1707
1841
  *
1708
1842
  * Optional: runtimes older than the descriptor omit it. Treat a missing
@@ -1755,7 +1889,7 @@ declare function getEtch(options?: ConnectOptions): Etch;
1755
1889
  * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for
1756
1890
  * the known values.
1757
1891
  */
1758
- type EtchApiErrorCode = 'BLOCK_NOT_FOUND' | 'WRONG_BLOCK_TYPE' | 'READONLY' | 'INVALID_ARGUMENT' | 'LOOP_NOT_FOUND' | 'DATA_SOURCE_NOT_FOUND' | 'STYLE_NOT_FOUND' | 'STYLESHEET_NOT_FOUND' | 'COMPONENT_NOT_FOUND' | 'POST_NOT_FOUND' | 'OPERATION_FAILED' | 'NOT_AVAILABLE' | (string & {});
1892
+ type EtchApiErrorCode = 'BLOCK_NOT_FOUND' | 'WRONG_BLOCK_TYPE' | 'READONLY' | 'INVALID_ARGUMENT' | 'LOOP_NOT_FOUND' | 'DATA_SOURCE_NOT_FOUND' | 'STYLE_NOT_FOUND' | 'STYLESHEET_NOT_FOUND' | 'COMPONENT_NOT_FOUND' | 'POST_NOT_FOUND' | 'ROUTE_NOT_FOUND' | 'OPERATION_FAILED' | 'NOT_AVAILABLE' | (string & {});
1759
1893
  /** Error thrown by the public Etch API (and by this client). */
1760
1894
  declare class EtchApiError extends Error {
1761
1895
  readonly code: EtchApiErrorCode;
@@ -1774,4 +1908,4 @@ declare function isEtchApiError(value: unknown): value is EtchApiError;
1774
1908
  */
1775
1909
  declare const ETCH_API_VERSION = "0.x";
1776
1910
 
1777
- export { type ArrayComponentProperty, type BlockLoopBinding, type BlockPatch, type BooleanComponentProperty, type BooleanParam, type BridgeConnectOptions, type ClassComponentProperty, type ColorScheme, type ComponentPatch, type ComponentProperty, type ComponentPropertyBase, type ConditionComponentProperty, type ConnectOptions, type CopyObject, type CustomField, type CustomFieldAssignment, type CustomFieldGroup, type CustomFieldType, ETCH_API_VERSION, type Etch, type EtchAiApi, EtchApiError, type EtchApiErrorCode, type EtchBlockCommon, type EtchBlockContext, type EtchBlockJson, type EtchBlockOptions, type EtchBlockScript, type EtchBlockType, type EtchBlockTypeName, type EtchBlocksApi, type EtchBridgeConnection, type EtchCapabilities, type EtchCapability, type EtchComponentBlockJson, type EtchComponentsApi, type EtchConditionBlockJson, type EtchDataSource, type EtchDataSourcePatch, type EtchDataSourceType, type EtchDataSourcesApi, type EtchDynamicElementBlockJson, type EtchDynamicImageBlockJson, type EtchElementBlockJson, type EtchEnvironment, type EtchFieldsApi, type EtchHistoryApi, type EtchHtmlAttributes, type EtchLoop, type EtchLoopBlockJson, type EtchLoopConfig, type EtchLoopObj, type EtchLoopsApi, type EtchNavigationApi, type EtchPassthroughBlockJson, type EtchPostContentBlockJson, type EtchProduct, type EtchRawHtmlBlockJson, type EtchRenderBlockJson, type EtchSkillsApi, type EtchSlotContentBlockJson, type EtchSlotPlaceholderBlockJson, type EtchStylesApi, type EtchStylesheetsApi, type EtchSvgBlockJson, type EtchTextBlockJson, type EtchUiApi, type ExternalAiState, type FindBlocksPredicate, type GroupComponentProperty, type GutenbergBlock, type LoopParamRef, type MetaQueryItem, type NavigationPlace, type NumberComponentProperty, type NumericParam, type ObjectComponentProperty, type PostCustomFieldGroupEntry, type PostCustomFieldValueEntry, type PostCustomFieldValueResponse, type PostCustomFieldValuesResponse, type PostSummary, type PublicBlockJson, type PublicComponentJson, type PublicComponentSummary, type RepeaterComponentProperty, type ResolvedCustomField, type SelectOptionsString, type SkillDetail, type SkillSummary, type StringComponentProperty, type StyleListFilter, type StylePatch, type StyleSelectorType, type StyleSummary, type StylesheetInput, type StylesheetPatch, type StylesheetSummary, type StylesheetType, type TaxQueryItem, type TemplateSummary, type WpQueryArgs, type WpTermsArgs, type WpUsersArgs, getEtch, isEtchApiError, isEtchAvailable };
1911
+ export { type ArrayComponentProperty, type BlockLoopBinding, type BlockPatch, type BooleanComponentProperty, type BooleanParam, type BridgeConnectOptions, type ClassComponentProperty, type ColorScheme, type ComponentPatch, type ComponentProperty, type ComponentPropertyBase, type ConditionComponentProperty, type ConnectOptions, type CopyObject, type CustomField, type CustomFieldAssignment, type CustomFieldGroup, type CustomFieldType, ETCH_API_VERSION, type Etch, type EtchAiApi, EtchApiError, type EtchApiErrorCode, type EtchBlockCommon, type EtchBlockContext, type EtchBlockJson, type EtchBlockOptions, type EtchBlockScript, type EtchBlockType, type EtchBlockTypeName, type EtchBlocksApi, type EtchBridgeConnection, type EtchCapabilities, type EtchCapability, type EtchComponentBlockJson, type EtchComponentsApi, type EtchConditionBlockJson, type EtchDataSource, type EtchDataSourcePatch, type EtchDataSourceType, type EtchDataSourcesApi, type EtchDynamicElementBlockJson, type EtchDynamicImageBlockJson, type EtchElementBlockJson, type EtchEnvironment, type EtchFieldsApi, type EtchHistoryApi, type EtchHtmlAttributes, type EtchLoop, type EtchLoopBlockJson, type EtchLoopConfig, type EtchLoopObj, type EtchLoopsApi, type EtchNavigationApi, type EtchPassthroughBlockJson, type EtchPostContentBlockJson, type EtchProduct, type EtchRawHtmlBlockJson, type EtchRenderBlockJson, type EtchRoute, type EtchRouteInput, type EtchRoutePatch, type EtchRouteSegmentKind, type EtchRouteSource, type EtchRoutedPath, type EtchRoutesApi, type EtchSkillsApi, type EtchSlotContentBlockJson, type EtchSlotPlaceholderBlockJson, type EtchStylesApi, type EtchStylesheetsApi, type EtchSvgBlockJson, type EtchTextBlockJson, type EtchUiApi, type ExternalAiState, type FindBlocksPredicate, type GroupComponentProperty, type GutenbergBlock, type LoopParamRef, type MetaQueryItem, type NavigationPlace, type NumberComponentProperty, type NumericParam, type ObjectComponentProperty, type PostCustomFieldGroupEntry, type PostCustomFieldValueEntry, type PostCustomFieldValueResponse, type PostCustomFieldValuesResponse, type PostSummary, type PublicBlockJson, type PublicComponentJson, type PublicComponentSummary, type RepeaterComponentProperty, type ResolvedCustomField, type SelectOptionsString, type SkillDetail, type SkillSummary, type StringComponentProperty, type StyleListFilter, type StylePatch, type StyleSelectorType, type StyleSummary, type StylesheetInput, type StylesheetPatch, type StylesheetSummary, type StylesheetType, type TaxQueryItem, type TemplateSummary, type WpQueryArgs, type WpTermsArgs, type WpUsersArgs, getEtch, isEtchApiError, isEtchAvailable };
package/dist/index.d.ts CHANGED
@@ -717,12 +717,16 @@ type EtchProduct = 'etch-wp' | 'etch-studio' | (string & {});
717
717
  *
718
718
  * - `fields` — `etch.fields.*` is backed by a real field store.
719
719
  * - `templates` — template posts exist and `navigation.goTo('templates')` works.
720
+ * On Studio a template post is a record of a template content type, and
721
+ * `goTo('templates')` opens the content hub, where they live.
722
+ * - `routes` — `etch.routes.*` is backed: the site's URLs are a route tree the
723
+ * author draws, and each route picks a template for the records it serves.
720
724
  * - `wpMedia` — media ids resolve against the **WordPress media library**, so a
721
725
  * URL can be turned into an id through `/wp/v2/media`. Without it the ids are
722
726
  * the product's own asset store instead. Either way an `etch/dynamic-image`
723
727
  * block binds the same: a numeric `mediaId` attribute.
724
728
  */
725
- type EtchCapability = 'loops' | 'dataSources' | 'fields' | 'templates' | 'wpMedia';
729
+ type EtchCapability = 'loops' | 'dataSources' | 'fields' | 'templates' | 'wpMedia' | 'routes';
726
730
  /**
727
731
  * Capability map. Every key is optional on purpose: a runtime older than a
728
732
  * capability's introduction simply omits it, so **absent means unavailable**.
@@ -1246,9 +1250,11 @@ interface EtchComponentsApi {
1246
1250
  * - `content-hub` — the pages/posts browser
1247
1251
  * - `style-manager` — the global style manager
1248
1252
  * - `loop-manager` — the loop manager
1253
+ * - `data-manager` — the data source manager
1249
1254
  * - `asset-manager` — the asset (media) library
1255
+ * - `routes` — the routes manager
1250
1256
  */
1251
- type NavigationPlace = 'builder' | 'templates' | 'style-manager' | 'content-hub' | 'loop-manager' | 'data-manager' | 'asset-manager';
1257
+ type NavigationPlace = 'builder' | 'templates' | 'style-manager' | 'content-hub' | 'loop-manager' | 'data-manager' | 'asset-manager' | 'routes';
1252
1258
  /** Lightweight post entry returned by `navigation.listPostsAsync()`. */
1253
1259
  interface PostSummary {
1254
1260
  /** Post id. */
@@ -1293,6 +1299,126 @@ interface EtchNavigationApi {
1293
1299
  listTemplatesAsync(): Promise<TemplateSummary[]>;
1294
1300
  }
1295
1301
 
1302
+ /**
1303
+ * The site's route tree and the `etch.routes` API surface.
1304
+ *
1305
+ * Routes are a tree of segments hanging from the site root; a route's URL is
1306
+ * its ancestors' segments joined by `/`. What a segment is depends on how it is
1307
+ * written:
1308
+ *
1309
+ * - `''` — the site root. Exactly one, never created or deleted.
1310
+ * - `about` — static: lowercase letters and digits joined by `-` or `_`.
1311
+ * Serves at most one record.
1312
+ * - `(marketing)` — a group: nests routes without adding to the URL. Serves
1313
+ * nothing itself.
1314
+ * - `{this.title.toSlug()}` — dynamic: resolved once per record it serves,
1315
+ * from that record's fields. Serves any mix of records and whole content
1316
+ * types. `this.slug` and `this.isHomepage` cannot be used — the routes
1317
+ * decide them.
1318
+ *
1319
+ * Records are what `navigation.listPostsAsync()` lists, and their content type
1320
+ * is its `postType` — so the fields below keep the `post` names. Templates are
1321
+ * records of a template content type (`navigation.listTemplatesAsync()`). A
1322
+ * route renders the records it serves through its template, or the nearest
1323
+ * ancestor's when it has none.
1324
+ *
1325
+ * Check `etch.environment.capabilities.routes === true` before using this
1326
+ * namespace — a runtime without it omits the whole thing.
1327
+ */
1328
+ /** What a route is, read off its segment. */
1329
+ type EtchRouteSegmentKind = 'root' | 'static' | 'group' | 'dynamic';
1330
+ /** What a route serves: these records, plus every record of these content types. */
1331
+ interface EtchRouteSource {
1332
+ /** Record ids, as `navigation.listPostsAsync()` returns them. */
1333
+ postIds: number[];
1334
+ /** Content type keys — `PostSummary.postType`. Dynamic routes only. */
1335
+ postTypes: string[];
1336
+ }
1337
+ /** One node of the route tree. */
1338
+ interface EtchRoute {
1339
+ /** Opaque string id. */
1340
+ id: string;
1341
+ /** The route this one sits inside. Absent only on the site root. */
1342
+ parentId?: string;
1343
+ /** This route's own part of the URL — see the module docs for the grammar. */
1344
+ segment: string;
1345
+ kind: EtchRouteSegmentKind;
1346
+ source: EtchRouteSource;
1347
+ /** Id of the template record. Absent means inherited from the nearest ancestor. */
1348
+ templateId?: number;
1349
+ }
1350
+ /** A route to create with `routes.createAsync()`. */
1351
+ interface EtchRouteInput {
1352
+ segment: string;
1353
+ /** Defaults to the site root. */
1354
+ parentId?: string;
1355
+ /** Defaults to serving nothing; a missing list is empty. */
1356
+ source?: Partial<EtchRouteSource>;
1357
+ templateId?: number;
1358
+ }
1359
+ /** Fields of an {@link EtchRoute} that `routes.updateAsync()` can change. */
1360
+ interface EtchRoutePatch {
1361
+ /** Renames the route; everything beneath it moves with it. */
1362
+ segment?: string;
1363
+ /** Moves the route (and its subtree) inside another route. */
1364
+ parentId?: string;
1365
+ /** Replaces what the route serves, whole. A missing list is empty. */
1366
+ source?: Partial<EtchRouteSource>;
1367
+ /** `null` clears the template, so the route inherits again. */
1368
+ templateId?: number | null;
1369
+ }
1370
+ /**
1371
+ * A URL the tree resolves to, and what the build does with it: publishes a
1372
+ * record there, loses it to a more specific route (`shadowed`), has two records
1373
+ * fighting for it (`conflict`), or refuses the segment for a record (`invalid`).
1374
+ */
1375
+ interface EtchRoutedPath {
1376
+ routeId: string;
1377
+ /** The record served there. Absent for a static route that only holds its path. */
1378
+ postId?: number;
1379
+ /** The record's title. */
1380
+ title?: string;
1381
+ /** The URL path; the route as written when `status` is `invalid`. */
1382
+ path: string;
1383
+ status: 'published' | 'shadowed' | 'conflict' | 'invalid';
1384
+ /** Why it is not published. */
1385
+ reason?: string;
1386
+ }
1387
+ /**
1388
+ * The project's routes. Writes persist to the backend immediately — they do
1389
+ * not wait for {@link Etch.saveAsync}.
1390
+ */
1391
+ interface EtchRoutesApi {
1392
+ /** Every route, the site root included. */
1393
+ listAsync(): Promise<EtchRoute[]>;
1394
+ /**
1395
+ * Every URL the tree resolves to, as the build would publish it. Read this
1396
+ * after a change to see where records actually land.
1397
+ */
1398
+ getPathsAsync(): Promise<EtchRoutedPath[]>;
1399
+ /**
1400
+ * Add a route.
1401
+ * @throws {EtchApiError} `INVALID_ARGUMENT` on a malformed segment, a source
1402
+ * its kind forbids, a name a sibling already has, an unknown content type, or
1403
+ * a template that is not a template record.
1404
+ * @throws {EtchApiError} `ROUTE_NOT_FOUND` when `parentId` names no route.
1405
+ */
1406
+ createAsync(route: EtchRouteInput): Promise<EtchRoute>;
1407
+ /**
1408
+ * Change a route. Only the fields present are applied.
1409
+ * @throws {EtchApiError} `ROUTE_NOT_FOUND`
1410
+ * @throws {EtchApiError} `INVALID_ARGUMENT` — as `createAsync`, or a move
1411
+ * that would put a route inside itself.
1412
+ */
1413
+ updateAsync(routeId: string, patch: EtchRoutePatch): Promise<EtchRoute>;
1414
+ /**
1415
+ * Delete a route and every route beneath it.
1416
+ * @throws {EtchApiError} `ROUTE_NOT_FOUND`
1417
+ * @throws {EtchApiError} `INVALID_ARGUMENT` for the site root.
1418
+ */
1419
+ deleteAsync(routeId: string): Promise<void>;
1420
+ }
1421
+
1296
1422
  /**
1297
1423
  * Custom field group/value shapes and the `etch.fields` API surface.
1298
1424
  */
@@ -1651,6 +1777,14 @@ interface Etch {
1651
1777
  * property) before calling it.
1652
1778
  */
1653
1779
  dataSources?: EtchDataSourcesApi;
1780
+ /**
1781
+ * The site's route tree, and which template renders each route's posts.
1782
+ *
1783
+ * Optional: a runtime that does not back the `routes` capability omits the
1784
+ * namespace, so check `environment.capabilities.routes === true` (or the
1785
+ * presence of this property) before calling it.
1786
+ */
1787
+ routes?: EtchRoutesApi;
1654
1788
  /** Global style (CSS) definitions and CSS variables. */
1655
1789
  styles: EtchStylesApi;
1656
1790
  /** Global stylesheets and `@custom-media` definitions. */
@@ -1702,7 +1836,7 @@ interface Etch {
1702
1836
  readonly version: string;
1703
1837
  /**
1704
1838
  * What this runtime is and which optional surfaces it backs. Read this
1705
- * before using `loops`, `fields`, templates or WordPress media, and before
1839
+ * before using `loops`, `fields`, `routes`, templates or WordPress media, and before
1706
1840
  * authoring a block type that may not exist here.
1707
1841
  *
1708
1842
  * Optional: runtimes older than the descriptor omit it. Treat a missing
@@ -1755,7 +1889,7 @@ declare function getEtch(options?: ConnectOptions): Etch;
1755
1889
  * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for
1756
1890
  * the known values.
1757
1891
  */
1758
- type EtchApiErrorCode = 'BLOCK_NOT_FOUND' | 'WRONG_BLOCK_TYPE' | 'READONLY' | 'INVALID_ARGUMENT' | 'LOOP_NOT_FOUND' | 'DATA_SOURCE_NOT_FOUND' | 'STYLE_NOT_FOUND' | 'STYLESHEET_NOT_FOUND' | 'COMPONENT_NOT_FOUND' | 'POST_NOT_FOUND' | 'OPERATION_FAILED' | 'NOT_AVAILABLE' | (string & {});
1892
+ type EtchApiErrorCode = 'BLOCK_NOT_FOUND' | 'WRONG_BLOCK_TYPE' | 'READONLY' | 'INVALID_ARGUMENT' | 'LOOP_NOT_FOUND' | 'DATA_SOURCE_NOT_FOUND' | 'STYLE_NOT_FOUND' | 'STYLESHEET_NOT_FOUND' | 'COMPONENT_NOT_FOUND' | 'POST_NOT_FOUND' | 'ROUTE_NOT_FOUND' | 'OPERATION_FAILED' | 'NOT_AVAILABLE' | (string & {});
1759
1893
  /** Error thrown by the public Etch API (and by this client). */
1760
1894
  declare class EtchApiError extends Error {
1761
1895
  readonly code: EtchApiErrorCode;
@@ -1774,4 +1908,4 @@ declare function isEtchApiError(value: unknown): value is EtchApiError;
1774
1908
  */
1775
1909
  declare const ETCH_API_VERSION = "0.x";
1776
1910
 
1777
- export { type ArrayComponentProperty, type BlockLoopBinding, type BlockPatch, type BooleanComponentProperty, type BooleanParam, type BridgeConnectOptions, type ClassComponentProperty, type ColorScheme, type ComponentPatch, type ComponentProperty, type ComponentPropertyBase, type ConditionComponentProperty, type ConnectOptions, type CopyObject, type CustomField, type CustomFieldAssignment, type CustomFieldGroup, type CustomFieldType, ETCH_API_VERSION, type Etch, type EtchAiApi, EtchApiError, type EtchApiErrorCode, type EtchBlockCommon, type EtchBlockContext, type EtchBlockJson, type EtchBlockOptions, type EtchBlockScript, type EtchBlockType, type EtchBlockTypeName, type EtchBlocksApi, type EtchBridgeConnection, type EtchCapabilities, type EtchCapability, type EtchComponentBlockJson, type EtchComponentsApi, type EtchConditionBlockJson, type EtchDataSource, type EtchDataSourcePatch, type EtchDataSourceType, type EtchDataSourcesApi, type EtchDynamicElementBlockJson, type EtchDynamicImageBlockJson, type EtchElementBlockJson, type EtchEnvironment, type EtchFieldsApi, type EtchHistoryApi, type EtchHtmlAttributes, type EtchLoop, type EtchLoopBlockJson, type EtchLoopConfig, type EtchLoopObj, type EtchLoopsApi, type EtchNavigationApi, type EtchPassthroughBlockJson, type EtchPostContentBlockJson, type EtchProduct, type EtchRawHtmlBlockJson, type EtchRenderBlockJson, type EtchSkillsApi, type EtchSlotContentBlockJson, type EtchSlotPlaceholderBlockJson, type EtchStylesApi, type EtchStylesheetsApi, type EtchSvgBlockJson, type EtchTextBlockJson, type EtchUiApi, type ExternalAiState, type FindBlocksPredicate, type GroupComponentProperty, type GutenbergBlock, type LoopParamRef, type MetaQueryItem, type NavigationPlace, type NumberComponentProperty, type NumericParam, type ObjectComponentProperty, type PostCustomFieldGroupEntry, type PostCustomFieldValueEntry, type PostCustomFieldValueResponse, type PostCustomFieldValuesResponse, type PostSummary, type PublicBlockJson, type PublicComponentJson, type PublicComponentSummary, type RepeaterComponentProperty, type ResolvedCustomField, type SelectOptionsString, type SkillDetail, type SkillSummary, type StringComponentProperty, type StyleListFilter, type StylePatch, type StyleSelectorType, type StyleSummary, type StylesheetInput, type StylesheetPatch, type StylesheetSummary, type StylesheetType, type TaxQueryItem, type TemplateSummary, type WpQueryArgs, type WpTermsArgs, type WpUsersArgs, getEtch, isEtchApiError, isEtchAvailable };
1911
+ export { type ArrayComponentProperty, type BlockLoopBinding, type BlockPatch, type BooleanComponentProperty, type BooleanParam, type BridgeConnectOptions, type ClassComponentProperty, type ColorScheme, type ComponentPatch, type ComponentProperty, type ComponentPropertyBase, type ConditionComponentProperty, type ConnectOptions, type CopyObject, type CustomField, type CustomFieldAssignment, type CustomFieldGroup, type CustomFieldType, ETCH_API_VERSION, type Etch, type EtchAiApi, EtchApiError, type EtchApiErrorCode, type EtchBlockCommon, type EtchBlockContext, type EtchBlockJson, type EtchBlockOptions, type EtchBlockScript, type EtchBlockType, type EtchBlockTypeName, type EtchBlocksApi, type EtchBridgeConnection, type EtchCapabilities, type EtchCapability, type EtchComponentBlockJson, type EtchComponentsApi, type EtchConditionBlockJson, type EtchDataSource, type EtchDataSourcePatch, type EtchDataSourceType, type EtchDataSourcesApi, type EtchDynamicElementBlockJson, type EtchDynamicImageBlockJson, type EtchElementBlockJson, type EtchEnvironment, type EtchFieldsApi, type EtchHistoryApi, type EtchHtmlAttributes, type EtchLoop, type EtchLoopBlockJson, type EtchLoopConfig, type EtchLoopObj, type EtchLoopsApi, type EtchNavigationApi, type EtchPassthroughBlockJson, type EtchPostContentBlockJson, type EtchProduct, type EtchRawHtmlBlockJson, type EtchRenderBlockJson, type EtchRoute, type EtchRouteInput, type EtchRoutePatch, type EtchRouteSegmentKind, type EtchRouteSource, type EtchRoutedPath, type EtchRoutesApi, type EtchSkillsApi, type EtchSlotContentBlockJson, type EtchSlotPlaceholderBlockJson, type EtchStylesApi, type EtchStylesheetsApi, type EtchSvgBlockJson, type EtchTextBlockJson, type EtchUiApi, type ExternalAiState, type FindBlocksPredicate, type GroupComponentProperty, type GutenbergBlock, type LoopParamRef, type MetaQueryItem, type NavigationPlace, type NumberComponentProperty, type NumericParam, type ObjectComponentProperty, type PostCustomFieldGroupEntry, type PostCustomFieldValueEntry, type PostCustomFieldValueResponse, type PostCustomFieldValuesResponse, type PostSummary, type PublicBlockJson, type PublicComponentJson, type PublicComponentSummary, type RepeaterComponentProperty, type ResolvedCustomField, type SelectOptionsString, type SkillDetail, type SkillSummary, type StringComponentProperty, type StyleListFilter, type StylePatch, type StyleSelectorType, type StyleSummary, type StylesheetInput, type StylesheetPatch, type StylesheetSummary, type StylesheetType, type TaxQueryItem, type TemplateSummary, type WpQueryArgs, type WpTermsArgs, type WpUsersArgs, getEtch, isEtchApiError, isEtchAvailable };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";AA0BO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAGvC,WAAA,CAAY,MAAwB,OAAA,EAAiB;AACpD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACb;AACD;AAGO,SAAS,eAAe,KAAA,EAAuC;AACrE,EAAA,OACC,KAAA,YAAiB,YAAA,IAChB,KAAA,EAAiB,IAAA,KAAS,cAAA;AAE7B;;;AClCO,IAAM,gBAAA,GAAmB;;;ACIhC,SAAS,QAAA,GAA6B;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,OAAO,KAAA,CAAM,IAAA;AACd;AAMO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,UAAS,KAAM,MAAA;AACvB;AAGA,SAAS,QAAQ,OAAA,EAAgC;AAChD,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,IAAA;AACnC;AAOA,SAAS,kBAAA,CAAmB,WAAmB,OAAA,EAAuB;AACrE,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAS,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AACrD,EAAA,OAAA,CAAQ,IAAA;AAAA,IACP,CAAA,qDAAA,EAAwD,SAAS,CAAA,wBAAA,EAC9C,OAAO,CAAA,sBAAA;AAAA,GAC3B;AACD;AAsBO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EAAS;AAC3D,EAAA,MAAM,OAAO,QAAA,EAAS;AACtB,EAAA,IAAI,CAAC,IAAA,EAAM;AACV,IAAA,MAAM,IAAI,YAAA;AAAA,MACT,eAAA;AAAA,MACA;AAAA,KAED;AAAA,EACD;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,KAAY,UAAA,EAAY;AACvC,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,QAAQ,UAAA,EAAY;AACvB,IAAA,kBAAA;AAAA,MACC,OAAA,CAAQ,UAAA;AAAA,MACR,KAAK,UAAA,IAAc;AAAA,KACpB;AAAA,EACD;AACA,EAAA,OAAO,IAAA;AACR","file":"index.js","sourcesContent":["/**\n * Error codes thrown by the public `window.etch` API.\n *\n * The API throws typed errors (rather than returning sentinels) so that AI\n * assistants and plugin authors can `try`/`catch` and react to a precise cause.\n *\n * The union ends with `(string & {})` so that codes added by newer Etch\n * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for\n * the known values.\n */\nexport type EtchApiErrorCode =\n\t| 'BLOCK_NOT_FOUND'\n\t| 'WRONG_BLOCK_TYPE'\n\t| 'READONLY'\n\t| 'INVALID_ARGUMENT'\n\t| 'LOOP_NOT_FOUND'\n\t| 'DATA_SOURCE_NOT_FOUND'\n\t| 'STYLE_NOT_FOUND'\n\t| 'STYLESHEET_NOT_FOUND'\n\t| 'COMPONENT_NOT_FOUND'\n\t| 'POST_NOT_FOUND'\n\t| 'OPERATION_FAILED'\n\t| 'NOT_AVAILABLE'\n\t| (string & {});\n\n/** Error thrown by the public Etch API (and by this client). */\nexport class EtchApiError extends Error {\n\treadonly code: EtchApiErrorCode;\n\n\tconstructor(code: EtchApiErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EtchApiError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Narrow an unknown caught value to an {@link EtchApiError}. */\nexport function isEtchApiError(value: unknown): value is EtchApiError {\n\treturn (\n\t\tvalue instanceof EtchApiError ||\n\t\t(value as Error)?.name === 'EtchApiError'\n\t);\n}\n","/**\n * Version of the Etch scripting **contract** this package targets, independent\n * of the Etch product version and of this package's own npm version.\n *\n * `0.x` signals the surface is **experimental** and may change without a major\n * bump until it stabilizes. It matches the value returned by the runtime's\n * {@link Etch.apiVersion} getter on `window.etch`.\n */\nexport const ETCH_API_VERSION = '0.x';\n","import type { ConnectOptions, Etch } from './contract';\nimport { EtchApiError } from './errors';\nimport { ETCH_API_VERSION } from './version';\n\ndeclare global {\n\tinterface Window {\n\t\t/** The Etch scripting API, present once the builder has loaded. */\n\t\tetch?: Etch;\n\t}\n}\n\n/** Read `window.etch` from whatever global object exists, or `undefined`. */\nfunction readEtch(): Etch | undefined {\n\tconst scope = globalThis as { etch?: Etch };\n\treturn scope.etch;\n}\n\n/**\n * Whether the Etch scripting API is present on the page. Use this to guard code\n * that should no-op when not running inside the builder.\n */\nexport function isEtchAvailable(): boolean {\n\treturn readEtch() !== undefined;\n}\n\n/** The major version number of a semver-ish string, or `null` if unparseable. */\nfunction majorOf(version: string): number | null {\n\tconst match = /^\\D*(\\d+)/.exec(version);\n\treturn match ? Number(match[1]) : null;\n}\n\n/**\n * Best-effort compatibility check for `0.x` runtimes that have no native\n * `connect()`. Warns (does not throw) when the requested major differs from the\n * runtime's, so experimental consumers aren't hard-blocked.\n */\nfunction warnIfIncompatible(requested: string, runtime: string): void {\n\tconst want = majorOf(requested);\n\tconst have = majorOf(runtime);\n\tif (want === null || have === null || want === have) return;\n\tconsole.warn(\n\t\t`[@digital-gravy/etch-public-api] Requested Etch API v${requested} but the ` +\n\t\t\t`page provides v${runtime}. Behavior may differ.`\n\t);\n}\n\n/**\n * Acquire the Etch scripting API from the page.\n *\n * The runtime lives on `window.etch` (injected by the builder); this returns it\n * typed. When the page exposes a future stable runtime with a native\n * `connect()`, version negotiation is delegated to it. On today's `0.x`\n * runtime, the global is returned directly after a best-effort version check.\n *\n * @throws {EtchApiError} `NOT_AVAILABLE` when the builder is not present.\n *\n * @example\n * ```ts\n * import { getEtch } from '@etchwp/public-api';\n *\n * const etch = getEtch();\n * const ids = etch.blocks.find({ type: 'text' });\n * etch.blocks.setText(ids[0], 'Hello');\n * await etch.saveAsync();\n * ```\n */\nexport function getEtch(options: ConnectOptions = {}): Etch {\n\tconst etch = readEtch();\n\tif (!etch) {\n\t\tthrow new EtchApiError(\n\t\t\t'NOT_AVAILABLE',\n\t\t\t'window.etch is not available. The Etch builder is not loaded on this page, ' +\n\t\t\t\t'or getEtch() ran before it finished initializing.'\n\t\t);\n\t}\n\n\tif (typeof etch.connect === 'function') {\n\t\treturn etch.connect(options);\n\t}\n\n\tif (options.apiVersion) {\n\t\twarnIfIncompatible(\n\t\t\toptions.apiVersion,\n\t\t\tetch.apiVersion ?? ETCH_API_VERSION\n\t\t);\n\t}\n\treturn etch;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";AA2BO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAGvC,WAAA,CAAY,MAAwB,OAAA,EAAiB;AACpD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACb;AACD;AAGO,SAAS,eAAe,KAAA,EAAuC;AACrE,EAAA,OACC,KAAA,YAAiB,YAAA,IAChB,KAAA,EAAiB,IAAA,KAAS,cAAA;AAE7B;;;ACnCO,IAAM,gBAAA,GAAmB;;;ACIhC,SAAS,QAAA,GAA6B;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,OAAO,KAAA,CAAM,IAAA;AACd;AAMO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,UAAS,KAAM,MAAA;AACvB;AAGA,SAAS,QAAQ,OAAA,EAAgC;AAChD,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,IAAA;AACnC;AAOA,SAAS,kBAAA,CAAmB,WAAmB,OAAA,EAAuB;AACrE,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAS,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AACrD,EAAA,OAAA,CAAQ,IAAA;AAAA,IACP,CAAA,qDAAA,EAAwD,SAAS,CAAA,wBAAA,EAC9C,OAAO,CAAA,sBAAA;AAAA,GAC3B;AACD;AAsBO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EAAS;AAC3D,EAAA,MAAM,OAAO,QAAA,EAAS;AACtB,EAAA,IAAI,CAAC,IAAA,EAAM;AACV,IAAA,MAAM,IAAI,YAAA;AAAA,MACT,eAAA;AAAA,MACA;AAAA,KAED;AAAA,EACD;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,KAAY,UAAA,EAAY;AACvC,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,QAAQ,UAAA,EAAY;AACvB,IAAA,kBAAA;AAAA,MACC,OAAA,CAAQ,UAAA;AAAA,MACR,KAAK,UAAA,IAAc;AAAA,KACpB;AAAA,EACD;AACA,EAAA,OAAO,IAAA;AACR","file":"index.js","sourcesContent":["/**\n * Error codes thrown by the public `window.etch` API.\n *\n * The API throws typed errors (rather than returning sentinels) so that AI\n * assistants and plugin authors can `try`/`catch` and react to a precise cause.\n *\n * The union ends with `(string & {})` so that codes added by newer Etch\n * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for\n * the known values.\n */\nexport type EtchApiErrorCode =\n\t| 'BLOCK_NOT_FOUND'\n\t| 'WRONG_BLOCK_TYPE'\n\t| 'READONLY'\n\t| 'INVALID_ARGUMENT'\n\t| 'LOOP_NOT_FOUND'\n\t| 'DATA_SOURCE_NOT_FOUND'\n\t| 'STYLE_NOT_FOUND'\n\t| 'STYLESHEET_NOT_FOUND'\n\t| 'COMPONENT_NOT_FOUND'\n\t| 'POST_NOT_FOUND'\n\t| 'ROUTE_NOT_FOUND'\n\t| 'OPERATION_FAILED'\n\t| 'NOT_AVAILABLE'\n\t| (string & {});\n\n/** Error thrown by the public Etch API (and by this client). */\nexport class EtchApiError extends Error {\n\treadonly code: EtchApiErrorCode;\n\n\tconstructor(code: EtchApiErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EtchApiError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Narrow an unknown caught value to an {@link EtchApiError}. */\nexport function isEtchApiError(value: unknown): value is EtchApiError {\n\treturn (\n\t\tvalue instanceof EtchApiError ||\n\t\t(value as Error)?.name === 'EtchApiError'\n\t);\n}\n","/**\n * Version of the Etch scripting **contract** this package targets, independent\n * of the Etch product version and of this package's own npm version.\n *\n * `0.x` signals the surface is **experimental** and may change without a major\n * bump until it stabilizes. It matches the value returned by the runtime's\n * {@link Etch.apiVersion} getter on `window.etch`.\n */\nexport const ETCH_API_VERSION = '0.x';\n","import type { ConnectOptions, Etch } from './contract';\nimport { EtchApiError } from './errors';\nimport { ETCH_API_VERSION } from './version';\n\ndeclare global {\n\tinterface Window {\n\t\t/** The Etch scripting API, present once the builder has loaded. */\n\t\tetch?: Etch;\n\t}\n}\n\n/** Read `window.etch` from whatever global object exists, or `undefined`. */\nfunction readEtch(): Etch | undefined {\n\tconst scope = globalThis as { etch?: Etch };\n\treturn scope.etch;\n}\n\n/**\n * Whether the Etch scripting API is present on the page. Use this to guard code\n * that should no-op when not running inside the builder.\n */\nexport function isEtchAvailable(): boolean {\n\treturn readEtch() !== undefined;\n}\n\n/** The major version number of a semver-ish string, or `null` if unparseable. */\nfunction majorOf(version: string): number | null {\n\tconst match = /^\\D*(\\d+)/.exec(version);\n\treturn match ? Number(match[1]) : null;\n}\n\n/**\n * Best-effort compatibility check for `0.x` runtimes that have no native\n * `connect()`. Warns (does not throw) when the requested major differs from the\n * runtime's, so experimental consumers aren't hard-blocked.\n */\nfunction warnIfIncompatible(requested: string, runtime: string): void {\n\tconst want = majorOf(requested);\n\tconst have = majorOf(runtime);\n\tif (want === null || have === null || want === have) return;\n\tconsole.warn(\n\t\t`[@digital-gravy/etch-public-api] Requested Etch API v${requested} but the ` +\n\t\t\t`page provides v${runtime}. Behavior may differ.`\n\t);\n}\n\n/**\n * Acquire the Etch scripting API from the page.\n *\n * The runtime lives on `window.etch` (injected by the builder); this returns it\n * typed. When the page exposes a future stable runtime with a native\n * `connect()`, version negotiation is delegated to it. On today's `0.x`\n * runtime, the global is returned directly after a best-effort version check.\n *\n * @throws {EtchApiError} `NOT_AVAILABLE` when the builder is not present.\n *\n * @example\n * ```ts\n * import { getEtch } from '@etchwp/public-api';\n *\n * const etch = getEtch();\n * const ids = etch.blocks.find({ type: 'text' });\n * etch.blocks.setText(ids[0], 'Hello');\n * await etch.saveAsync();\n * ```\n */\nexport function getEtch(options: ConnectOptions = {}): Etch {\n\tconst etch = readEtch();\n\tif (!etch) {\n\t\tthrow new EtchApiError(\n\t\t\t'NOT_AVAILABLE',\n\t\t\t'window.etch is not available. The Etch builder is not loaded on this page, ' +\n\t\t\t\t'or getEtch() ran before it finished initializing.'\n\t\t);\n\t}\n\n\tif (typeof etch.connect === 'function') {\n\t\treturn etch.connect(options);\n\t}\n\n\tif (options.apiVersion) {\n\t\twarnIfIncompatible(\n\t\t\toptions.apiVersion,\n\t\t\tetch.apiVersion ?? ETCH_API_VERSION\n\t\t);\n\t}\n\treturn etch;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-gravy/etch-public-api",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "MIT-licensed typed client and contract for the Etch builder scripting API (window.etch). Etch itself is a separate proprietary product governed by its own commercial terms.",
5
5
  "license": "MIT",
6
6
  "repository": {