@digital-gravy/etch-public-api 0.7.0 → 0.7.2

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
@@ -26,13 +26,13 @@ Acquire it with `getEtch()`, guarded by `isEtchAvailable()` for code that might
26
26
  run outside the builder:
27
27
 
28
28
  ```ts
29
- import { getEtch, isEtchAvailable } from "@digital-gravy/etch-public-api";
29
+ import { getEtch, isEtchAvailable } from '@digital-gravy/etch-public-api';
30
30
 
31
31
  function run() {
32
- if (!isEtchAvailable()) return; // not running inside the Etch builder
33
- const etch = getEtch();
34
- const textIds = etch.blocks.find({ type: "text" });
35
- etch.blocks.setText(textIds[0], "Hello world");
32
+ if (!isEtchAvailable()) return; // not running inside the Etch builder
33
+ const etch = getEtch();
34
+ const textIds = etch.blocks.find({ type: 'text' });
35
+ etch.blocks.setText(textIds[0], 'Hello world');
36
36
  }
37
37
  ```
38
38
 
@@ -41,15 +41,16 @@ isn't on the page, so you can also `try`/`catch` it. If your script may run
41
41
  before the builder has finished loading, wait until it appears:
42
42
 
43
43
  ```ts
44
- import { getEtch, isEtchAvailable } from "@digital-gravy/etch-public-api";
44
+ import { getEtch, isEtchAvailable } from '@digital-gravy/etch-public-api';
45
45
 
46
46
  async function whenEtchReady(timeoutMs = 10_000) {
47
- const start = Date.now();
48
- while (!isEtchAvailable()) {
49
- if (Date.now() - start > timeoutMs) throw new Error("Etch did not load");
50
- await new Promise((resolve) => setTimeout(resolve, 100));
51
- }
52
- return getEtch();
47
+ const start = Date.now();
48
+ while (!isEtchAvailable()) {
49
+ if (Date.now() - start > timeoutMs)
50
+ throw new Error('Etch did not load');
51
+ await new Promise((resolve) => setTimeout(resolve, 100));
52
+ }
53
+ return getEtch();
53
54
  }
54
55
 
55
56
  const etch = await whenEtchReady();
@@ -59,12 +60,12 @@ const etch = await whenEtchReady();
59
60
 
60
61
  ```ts
61
62
  // Read
62
- const textIds = etch.blocks.find({ type: "etch/text" });
63
+ const textIds = etch.blocks.find({ type: 'etch/text' });
63
64
  const json = etch.blocks.getJson(textIds[0]);
64
65
 
65
66
  // Mutate (routes through the same guarded paths as the UI; undo/redo works)
66
- etch.blocks.setText(textIds[0], "Hello world");
67
- etch.blocks.addClass(textIds[0], "lead");
67
+ etch.blocks.setText(textIds[0], 'Hello world');
68
+ etch.blocks.addClass(textIds[0], 'lead');
68
69
 
69
70
  // Persist (blocks/styles wait for save; stylesheets/components/fields persist immediately)
70
71
  await etch.saveAsync();
@@ -74,22 +75,22 @@ await etch.saveAsync();
74
75
 
75
76
  ```ts
76
77
  // Create a class or id rule
77
- const styleId = etch.styles.create(".lead", "font-size: 1.25rem;");
78
+ const styleId = etch.styles.create('.lead', 'font-size: 1.25rem;');
78
79
 
79
80
  // Find existing styles by selector type
80
- const classStyles = etch.styles.list({ type: "class" });
81
- const myStyle = etch.styles.list().find((s) => s.selector === ".lead");
81
+ const classStyles = etch.styles.list({ type: 'class' });
82
+ const myStyle = etch.styles.list().find((s) => s.selector === '.lead');
82
83
  console.log(myStyle?.id); // the id you pass to blocks.addClass etc.
83
84
 
84
85
  // Global CSS custom properties (default collection)
85
- etch.styles.setVariable("--brand", "#0af");
86
- etch.styles.getVariable("--brand"); // "#0af"
86
+ etch.styles.setVariable('--brand', '#0af');
87
+ etch.styles.getVariable('--brand'); // "#0af"
87
88
 
88
89
  // Variable methods accept an optional collection for multi-collection :root setups
89
- etch.styles.setVariable("--brand", "#0af", "theme-a");
90
- etch.styles.getVariable("--brand", "theme-a"); // "#0af"
91
- etch.styles.listVariables("theme-a");
92
- etch.styles.removeVariable("--brand", "theme-a");
90
+ etch.styles.setVariable('--brand', '#0af', 'theme-a');
91
+ etch.styles.getVariable('--brand', 'theme-a'); // "#0af"
92
+ etch.styles.listVariables('theme-a');
93
+ etch.styles.removeVariable('--brand', 'theme-a');
93
94
  ```
94
95
 
95
96
  > **Note:** The `collection` field on style **objects** (`StyleSummary`) and the
@@ -106,7 +107,7 @@ for direct inspection or mutation, then save and exit when done:
106
107
 
107
108
  ```ts
108
109
  // Find a component block
109
- const [compId] = etch.blocks.find({ type: "etch/component" });
110
+ const [compId] = etch.blocks.find({ type: 'etch/component' });
110
111
 
111
112
  // Enter edit mode — the component's block tree becomes accessible
112
113
  etch.blocks.enterComponentEditMode(compId);
@@ -137,19 +138,19 @@ key must be a property declared by the component definition, or the call throws
137
138
  `INVALID_ARGUMENT`.
138
139
 
139
140
  ```ts
140
- const [cardId] = etch.blocks.find({ type: "etch/component" });
141
+ const [cardId] = etch.blocks.find({ type: 'etch/component' });
141
142
 
142
143
  // Set a prop the component declares
143
- etch.blocks.setAttribute(cardId, "title", "Hello world");
144
+ etch.blocks.setAttribute(cardId, 'title', 'Hello world');
144
145
 
145
146
  // Read it back
146
- etch.blocks.getAttribute(cardId, "title"); // "Hello world"
147
+ etch.blocks.getAttribute(cardId, 'title'); // "Hello world"
147
148
 
148
149
  // Or patch several props at once
149
- etch.blocks.update(cardId, { attributes: { title: "Hi", variant: "primary" } });
150
+ etch.blocks.update(cardId, { attributes: { title: 'Hi', variant: 'primary' } });
150
151
 
151
152
  // An undeclared key is rejected
152
- etch.blocks.setAttribute(cardId, "notAProp", "x"); // ✗ throws INVALID_ARGUMENT
153
+ etch.blocks.setAttribute(cardId, 'notAProp', 'x'); // ✗ throws INVALID_ARGUMENT
153
154
  ```
154
155
 
155
156
  > Values are stored as-is. For typed props such as classes or repeater/group
@@ -161,14 +162,14 @@ Methods throw a typed `EtchApiError` with a `code`, rather than returning
161
162
  sentinels:
162
163
 
163
164
  ```ts
164
- import { isEtchApiError } from "@digital-gravy/etch-public-api";
165
+ import { isEtchApiError } from '@digital-gravy/etch-public-api';
165
166
 
166
167
  try {
167
- etch.blocks.getJson("does-not-exist");
168
+ etch.blocks.getJson('does-not-exist');
168
169
  } catch (err) {
169
- if (isEtchApiError(err)) {
170
- console.warn(err.code, err.message); // e.g. "BLOCK_NOT_FOUND"
171
- }
170
+ if (isEtchApiError(err)) {
171
+ console.warn(err.code, err.message); // e.g. "BLOCK_NOT_FOUND"
172
+ }
172
173
  }
173
174
  ```
174
175
 
@@ -182,8 +183,8 @@ experimental, **prefer feature detection** over version comparison:
182
183
 
183
184
  ```ts
184
185
  const etch = getEtch();
185
- if (typeof etch.blocks.someNewMethod === "function") {
186
- // safe to use
186
+ if (typeof etch.blocks.someNewMethod === 'function') {
187
+ // safe to use
187
188
  }
188
189
  ```
189
190
 
@@ -194,7 +195,7 @@ targets, and failing fast when the runtime can't satisfy it:
194
195
 
195
196
  ```ts
196
197
  // Reserved API — shape of versioned access once the contract is stable:
197
- const etch = getEtch({ apiVersion: "^1.0", id: "my-plugin" });
198
+ const etch = getEtch({ apiVersion: '^1.0', id: 'my-plugin' });
198
199
  // └─ delegates to window.etch.connect({ apiVersion: "^1.0", id }) when present,
199
200
  // yielding a version-pinned instance (throws on an incompatible runtime).
200
201
  ```
@@ -212,19 +213,19 @@ results narrow by `type`:
212
213
  ```ts
213
214
  const block = etch.blocks.getJson(id);
214
215
 
215
- if (block.type === "etch/text") {
216
- console.log(block.text); // narrowed to the text-block shape
217
- } else if (block.type === "etch/element") {
218
- console.log(block.tag, block.attributes);
216
+ if (block.type === 'etch/text') {
217
+ console.log(block.text); // narrowed to the text-block shape
218
+ } else if (block.type === 'etch/element') {
219
+ console.log(block.tag, block.attributes);
219
220
  }
220
221
 
221
222
  // Authoring is checked too — this is a type error (an `etch/text` has no `tag`):
222
223
  etch.blocks.create({
223
- type: "etch/text",
224
- version: 1,
225
- context: {},
226
- children: [],
227
- tag: "div", // ✗ type error
224
+ type: 'etch/text',
225
+ version: 1,
226
+ context: {},
227
+ children: [],
228
+ tag: 'div' // ✗ type error
228
229
  });
229
230
  ```
230
231
 
@@ -233,22 +234,24 @@ etch.blocks.create({
233
234
  Some block types recognise **special attributes** in addition to standard HTML:
234
235
 
235
236
  **`etch/dynamic-image`** — rendered as `<img>`:
237
+
236
238
  - `mediaId` — WordPress attachment ID. Etch fetches the media object and uses its URL as `src`, overriding any explicit `src`. Supports dynamic expressions (e.g. `{post.featured_image_id}`).
237
239
  - `useSrcSet` — `"true"` to generate a responsive `srcset` from the media (requires `mediaId`).
238
240
  - `maximumSize` — WordPress image size slug (e.g. `"large"`, `"full"`) used when resolving the image. Defaults to `"full"`.
239
241
 
240
242
  ```ts
241
- etch.blocks.setAttribute(imgBlockId, "mediaId", "{post.featured_image_id}");
242
- etch.blocks.setAttribute(imgBlockId, "useSrcSet", "true");
243
+ etch.blocks.setAttribute(imgBlockId, 'mediaId', '{post.featured_image_id}');
244
+ etch.blocks.setAttribute(imgBlockId, 'useSrcSet', 'true');
243
245
  ```
244
246
 
245
247
  **`etch/svg`** — inline SVG:
248
+
246
249
  - `src` — URL of an external `.svg` file. Etch fetches and inlines the SVG at render time. Supports dynamic expressions.
247
250
  - `stripColors` — `"true"` to strip `fill` and `stroke` colour declarations from the fetched SVG, so CSS can drive its colours instead.
248
251
 
249
252
  ```ts
250
- etch.blocks.setAttribute(svgBlockId, "src", "/icons/logo.svg");
251
- etch.blocks.setAttribute(svgBlockId, "stripColors", "true");
253
+ etch.blocks.setAttribute(svgBlockId, 'src', '/icons/logo.svg');
254
+ etch.blocks.setAttribute(svgBlockId, 'stripColors', 'true');
252
255
  ```
253
256
 
254
257
  ### Types only
@@ -258,9 +261,9 @@ directly:
258
261
 
259
262
  ```ts
260
263
  import type {
261
- PublicBlockJson,
262
- EtchBlocksApi,
263
- } from "@digital-gravy/etch-public-api";
264
+ PublicBlockJson,
265
+ EtchBlocksApi
266
+ } from '@digital-gravy/etch-public-api';
264
267
  ```
265
268
 
266
269
  You can also work against the global directly — the package augments
@@ -273,14 +276,14 @@ is imported.
273
276
  - `EtchApiError` / `isEtchApiError()` / `EtchApiErrorCode` — typed errors.
274
277
  - `ETCH_API_VERSION` — the contract version this package targets (`0.x`).
275
278
  - The full contract as exported types:
276
- - **Blocks** — `Etch`, `EtchBlocksApi`, `EtchBlockJson`, `PublicBlockJson`, `FindBlocksPredicate`, `BlockPatch`, …
277
- - **Styles** — `EtchStylesApi`, `StyleSummary`, `StyleListFilter`, `StyleSelectorType`, `StylePatch`
278
- - **Stylesheets** — `EtchStylesheetsApi`, `StylesheetSummary`, `StylesheetInput`, `StylesheetPatch`
279
- - **Components** — `EtchComponentsApi`, `PublicComponentSummary`, `PublicComponentJson`, `ComponentPatch`, `ComponentProperty`, …
280
- - **Loops** — `EtchLoopsApi`, `EtchLoop`, `EtchLoopConfig`, `BlockLoopBinding`, …
281
- - **Navigation** — `EtchNavigationApi`, `NavigationPlace`, `PostSummary`, `TemplateSummary`
282
- - **Fields** — `EtchFieldsApi`, `CustomField`, `CustomFieldGroup`, …
283
- - **UI / History** — `EtchUiApi`, `EtchHistoryApi`, `ColorScheme`
279
+ - **Blocks** — `Etch`, `EtchBlocksApi`, `EtchBlockJson`, `PublicBlockJson`, `FindBlocksPredicate`, `BlockPatch`, …
280
+ - **Styles** — `EtchStylesApi`, `StyleSummary`, `StyleListFilter`, `StyleSelectorType`, `StylePatch`
281
+ - **Stylesheets** — `EtchStylesheetsApi`, `StylesheetSummary`, `StylesheetInput`, `StylesheetPatch`
282
+ - **Components** — `EtchComponentsApi`, `PublicComponentSummary`, `PublicComponentJson`, `ComponentPatch`, `ComponentProperty`, …
283
+ - **Loops** — `EtchLoopsApi`, `EtchLoop`, `EtchLoopConfig`, `BlockLoopBinding`, …
284
+ - **Navigation** — `EtchNavigationApi`, `NavigationPlace`, `PostSummary`, `TemplateSummary`
285
+ - **Fields** — `EtchFieldsApi`, `CustomField`, `CustomFieldGroup`, …
286
+ - **UI / History** — `EtchUiApi`, `EtchHistoryApi`, `ColorScheme`
284
287
 
285
288
  ## Versioning
286
289
 
package/dist/index.cjs CHANGED
@@ -47,7 +47,10 @@ function getEtch(options = {}) {
47
47
  return etch.connect(options);
48
48
  }
49
49
  if (options.apiVersion) {
50
- warnIfIncompatible(options.apiVersion, etch.apiVersion ?? ETCH_API_VERSION);
50
+ warnIfIncompatible(
51
+ options.apiVersion,
52
+ etch.apiVersion ?? ETCH_API_VERSION
53
+ );
51
54
  }
52
55
  return etch;
53
56
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";;;AAyBO,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,OAAO,KAAA,YAAiB,YAAA,IAAiB,KAAA,EAAiB,IAAA,KAAS,cAAA;AACpE;;;AC9BO,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;AAErD,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,CAAmB,OAAA,CAAQ,UAAA,EAAY,IAAA,CAAK,UAAA,IAAc,gBAAgB,CAAA;AAAA,EAC3E;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| '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 value instanceof EtchApiError || (value as Error)?.name === 'EtchApiError';\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\t// eslint-disable-next-line no-console\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(options.apiVersion, etch.apiVersion ?? ETCH_API_VERSION);\n\t}\n\treturn etch;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";;;AAyBO,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;;;ACjCO,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| '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"]}