@digital-gravy/etch-public-api 0.9.0 → 0.11.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
@@ -254,6 +254,35 @@ etch.blocks.setAttribute(svgBlockId, 'src', '/icons/logo.svg');
254
254
  etch.blocks.setAttribute(svgBlockId, 'stripColors', 'true');
255
255
  ```
256
256
 
257
+ ### Data sources
258
+
259
+ `etch.dataSources` is the `dataSources` capability's model of dynamic data, and
260
+ supersedes `etch.loops`. A source is defined once for the project under a `key`
261
+ and read anywhere an expression is accepted, through `data('key')`. The
262
+ namespace is **optional** — a runtime without the capability omits it.
263
+
264
+ ```ts
265
+ if (etch.environment?.capabilities.dataSources === true) {
266
+ const sources = etch.dataSources!;
267
+
268
+ await sources.createAsync({
269
+ key: 'courses',
270
+ name: 'Courses',
271
+ type: 'api',
272
+ metadata: { url: 'https://example.com/courses.json' }
273
+ });
274
+
275
+ // See the real shape before authoring paths against it
276
+ const value = await sources.resolveAsync('courses');
277
+
278
+ // A loop block points its target at the expression — there is no loop id
279
+ etch.blocks.update(loopBlockId, { target: "data('courses')" });
280
+ }
281
+ ```
282
+
283
+ Reads (`list`, `get`, `findDataSource`) are synchronous; writes persist to the
284
+ backend immediately and do not wait for `saveAsync()`.
285
+
257
286
  ### Skills
258
287
 
259
288
  `etch.skills` exposes the bundled, read-only authoring guides Etch ships (e.g.
@@ -317,6 +346,7 @@ is imported.
317
346
  - **Stylesheets** — `EtchStylesheetsApi`, `StylesheetSummary`, `StylesheetInput`, `StylesheetPatch`
318
347
  - **Components** — `EtchComponentsApi`, `PublicComponentSummary`, `PublicComponentJson`, `ComponentPatch`, `ComponentProperty`, …
319
348
  - **Loops** — `EtchLoopsApi`, `EtchLoop`, `EtchLoopConfig`, `BlockLoopBinding`, …
349
+ - **Data sources** — `EtchDataSourcesApi`, `EtchDataSource`, `EtchDataSourceType`, `EtchDataSourcePatch`
320
350
  - **Navigation** — `EtchNavigationApi`, `NavigationPlace`, `PostSummary`, `TemplateSummary`
321
351
  - **Fields** — `EtchFieldsApi`, `CustomField`, `CustomFieldGroup`, …
322
352
  - **Skills** — `EtchSkillsApi`, `SkillSummary`, `SkillDetail`
@@ -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,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"]}
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"]}
package/dist/index.d.cts CHANGED
@@ -340,15 +340,26 @@ interface EtchLoopBlockJson extends EtchBlockCommon {
340
340
  type: 'etch/loop';
341
341
  /** Variable name bound to the current item (e.g. `item`). */
342
342
  itemId: string;
343
- /** What the block iterates over (a dynamic path); omitted when bound via `loopId`. */
343
+ /**
344
+ * What the block iterates over.
345
+ *
346
+ * On a `dataSources` runtime this is the whole binding and is **required**:
347
+ * any dynamic expression yielding a list — `data('products').items`, an
348
+ * enclosing loop's `item.tags`, `range(1, 10)`. On a `loops` runtime it is
349
+ * optional and means a sub-path within the loop bound by `loopId`.
350
+ */
344
351
  target?: string;
345
352
  /** Variable name bound to the current index. */
346
353
  indexId?: string;
347
- /** Id of a registered loop definition this block is bound to. */
354
+ /**
355
+ * Id of a registered loop definition this block is bound to.
356
+ * **Requires the `loops` capability** — ignored where loops are not backed.
357
+ */
348
358
  loopId?: string;
349
359
  /**
350
360
  * Values for the bound loop's parameters. Every key must be a `$`-prefixed
351
361
  * parameter reference (e.g. `$count`).
362
+ * **Requires the `loops` capability** — ignored where loops are not backed.
352
363
  */
353
364
  loopParams?: Record<LoopParamRef, unknown>;
354
365
  }
@@ -390,7 +401,13 @@ interface EtchRawHtmlBlockJson extends EtchBlockCommon {
390
401
  /** The original, unsanitized HTML as authored. */
391
402
  unsafe: string;
392
403
  }
393
- /** A pass-through wrapper around a native Gutenberg block (`etch/passthrough`). */
404
+ /**
405
+ * A pass-through wrapper around a native Gutenberg block (`etch/passthrough`).
406
+ *
407
+ * **Not registered on every build** — this one is WordPress-only. Check
408
+ * `etch.environment.blockTypes` before authoring it; creating a block type the
409
+ * runtime does not register is rejected.
410
+ */
394
411
  interface EtchPassthroughBlockJson extends EtchBlockCommon {
395
412
  type: 'etch/passthrough';
396
413
  /** The wrapped Gutenberg block. */
@@ -402,8 +419,8 @@ interface EtchPassthroughBlockJson extends EtchBlockCommon {
402
419
  * time, such as a content-type field. Its children are the resolved JSON, so it
403
420
  * accepts no authored children.
404
421
  *
405
- * **Not registered on every build** — this one is Studio-only; creating a block
406
- * type the runtime does not register is rejected.
422
+ * **Not registered on every build** — this one is Studio-only. Check
423
+ * `etch.environment.blockTypes` before authoring it.
407
424
  */
408
425
  interface EtchRenderBlockJson extends EtchBlockCommon {
409
426
  type: 'etch/render';
@@ -484,9 +501,9 @@ interface BlockPatch {
484
501
  /** Replace the text content. Only valid on text blocks. */
485
502
  text?: string;
486
503
  /**
487
- * Re-point what the block resolves at: the expression an `etch/loop` block
488
- * iterates over, or the one an `etch/render` block renders. Only valid on
489
- * block types that carry a `target`.
504
+ * Re-point what the block resolves at: the iterated expression of an
505
+ * `etch/loop` block, or the rendered expression of an `etch/render` block.
506
+ * Only valid on those two types.
490
507
  */
491
508
  target?: string;
492
509
  }
@@ -662,6 +679,169 @@ interface EtchBlocksApi {
662
679
  saveComponentEditModeAsync(): Promise<void>;
663
680
  }
664
681
 
682
+ /**
683
+ * What this Etch runtime actually is, and which optional surfaces it backs.
684
+ *
685
+ * Etch ships as more than one product against **one** contract: `etch-wp` runs
686
+ * inside WordPress, `etch-studio` is standalone. They expose the same `etch`
687
+ * namespaces, but a namespace can be declared and not implemented — those
688
+ * methods throw `EtchApiError` with code `NOT_AVAILABLE`.
689
+ *
690
+ * `etch.environment` is how a caller finds that out **without** calling and
691
+ * catching. It is deliberately a description of the runtime rather than of the
692
+ * product: prefer `capabilities.loops` over `product === 'etch-wp'`, because
693
+ * capabilities move between products over time (the data manager, for one, is
694
+ * on its way to WordPress) while a product check silently rots the day they do.
695
+ */
696
+ /** Which build of Etch is running. Identity — not a capability check. */
697
+ type EtchProduct = 'etch-wp' | 'etch-studio' | (string & {});
698
+ /**
699
+ * Optional surfaces a runtime may or may not back.
700
+ *
701
+ * `loops` and `dataSources` are the two dynamic-data models, and they decide
702
+ * how an `etch/loop` block is authored — check which one you are on before
703
+ * writing one:
704
+ *
705
+ * - `loops` — a registry of named loop definitions. A loop block binds to one
706
+ * by `loopId` (plus `$`-prefixed `loopParams`), and `target` is an optional
707
+ * sub-path within it.
708
+ * - `dataSources` — named, typed project sources (`json`, `api`, `js`), reached
709
+ * through a `data('key')` expression. A loop block carries **only** a
710
+ * required `target`, and that target is any *dynamic expression* yielding a
711
+ * list — a data source, a path into one (`data('products').items`), nested
712
+ * data from an enclosing loop (`item.tags`), or a generated list
713
+ * (`range(1, 10)`). There is no `loopId`/`loopParams`. This model supersedes
714
+ * `loops` and is expected on both products eventually.
715
+ *
716
+ * The rest:
717
+ *
718
+ * - `fields` — `etch.fields.*` is backed by a real field store.
719
+ * - `templates` — template posts exist and `navigation.goTo('templates')` works.
720
+ * - `wpMedia` — media ids resolve against the **WordPress media library**, so a
721
+ * URL can be turned into an id through `/wp/v2/media`. Without it the ids are
722
+ * the product's own asset store instead. Either way an `etch/dynamic-image`
723
+ * block binds the same: a numeric `mediaId` attribute.
724
+ */
725
+ type EtchCapability = 'loops' | 'dataSources' | 'fields' | 'templates' | 'wpMedia';
726
+ /**
727
+ * Capability map. Every key is optional on purpose: a runtime older than a
728
+ * capability's introduction simply omits it, so **absent means unavailable**.
729
+ * Test with `=== true` rather than truthiness of a possibly-missing key.
730
+ */
731
+ type EtchCapabilities = Readonly<Partial<Record<EtchCapability, boolean>>>;
732
+ /** Runtime self-description, exposed as `etch.environment`. */
733
+ interface EtchEnvironment {
734
+ /**
735
+ * Which build this is — identity, for display and diagnostics. Every
736
+ * behavioral decision belongs to {@link capabilities} instead; a product
737
+ * check goes wrong the moment a capability moves between products.
738
+ */
739
+ readonly product: EtchProduct;
740
+ /** Which optional surfaces this runtime actually backs. */
741
+ readonly capabilities: EtchCapabilities;
742
+ /**
743
+ * Every block `type` this runtime can construct, sorted. Read this instead
744
+ * of assuming a block exists: `etch/passthrough` is WordPress-only and
745
+ * `etch/render` is Studio-only, and the list grows with each release.
746
+ */
747
+ readonly blockTypes: readonly string[];
748
+ }
749
+
750
+ /**
751
+ * Project data sources and the `etch.dataSources` API surface.
752
+ *
753
+ * Data sources are the `dataSources` capability's model of dynamic data, and
754
+ * they supersede {@link EtchLoopsApi | loops}: a named, typed source is defined
755
+ * once for the project and read anywhere an expression is accepted, through
756
+ * `data('key')`. A loop block points its `target` at one of those expressions
757
+ * rather than binding to a loop id.
758
+ *
759
+ * Check `etch.environment.capabilities.dataSources === true` before using this
760
+ * namespace — a runtime without it omits the whole thing.
761
+ */
762
+ /**
763
+ * A source type. The three built-ins are listed; the registry behind the
764
+ * runtime is open, so a host may serve types this contract has never heard of.
765
+ */
766
+ type EtchDataSourceType = 'json' | 'api' | 'js' | (string & {});
767
+ /**
768
+ * A project data source, identified by its `key` — the name `data('key')`
769
+ * calls it by.
770
+ */
771
+ interface EtchDataSource {
772
+ /**
773
+ * Stable identifier used in expressions: `data('products')`. Unique within
774
+ * the project, and part of every authored expression that reads the source,
775
+ * so renaming one breaks those expressions.
776
+ */
777
+ key: string;
778
+ /** Display name shown in the data manager. */
779
+ name: string;
780
+ /** Which kind of source this is; decides how `metadata` is read. */
781
+ type: EtchDataSourceType;
782
+ /**
783
+ * Type-specific configuration. The built-in types read:
784
+ *
785
+ * - `json` — `{ data: unknown }`, the payload served verbatim.
786
+ * - `api` — `{ url: string; method?: string; headers?: [string, string][] }`,
787
+ * fetched and parsed as JSON. A non-OK response fails the source.
788
+ * - `js` — `{ code: string }`, a function body run in a sandbox. Arguments
789
+ * passed by the expression (`data('rows', 5)`) arrive as `args`.
790
+ */
791
+ metadata: Record<string, unknown>;
792
+ }
793
+ /** Fields of an {@link EtchDataSource} that `updateAsync` can change. */
794
+ type EtchDataSourcePatch = Partial<EtchDataSource>;
795
+ /**
796
+ * Project data sources: definitions, and resolving one to its value.
797
+ *
798
+ * Reads are synchronous — the project's sources are loaded before `window.etch`
799
+ * exists. Writes persist to the backend immediately (they do not wait for
800
+ * {@link Etch.saveAsync}) and drop any value already resolved from the source,
801
+ * so the canvas re-reads it.
802
+ */
803
+ interface EtchDataSourcesApi {
804
+ /** Every data source defined for this project. */
805
+ list(): EtchDataSource[];
806
+ /**
807
+ * One data source by key.
808
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
809
+ */
810
+ get(key: string): EtchDataSource;
811
+ /**
812
+ * Fuzzy-search data sources by `name` or `key`, ranked best-first. Returns
813
+ * an empty array for a blank query or when nothing matches.
814
+ */
815
+ findDataSource(query: string): EtchDataSource[];
816
+ /**
817
+ * Define a new data source.
818
+ * @throws {EtchApiError} `INVALID_ARGUMENT` on a malformed source, or when
819
+ * the key is already taken.
820
+ */
821
+ createAsync(source: EtchDataSource): Promise<EtchDataSource>;
822
+ /**
823
+ * Change an existing data source. Only the fields present are applied.
824
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
825
+ */
826
+ updateAsync(key: string, patch: EtchDataSourcePatch): Promise<EtchDataSource>;
827
+ /**
828
+ * Delete a data source. Expressions still naming it resolve to `undefined`.
829
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
830
+ */
831
+ deleteAsync(key: string): Promise<void>;
832
+ /**
833
+ * Resolve a data source to its value, exactly as `data('key', ...args)`
834
+ * would on the canvas — read this before authoring the property paths that
835
+ * depend on its shape.
836
+ *
837
+ * A source that fails resolves to `undefined` rather than rejecting, which
838
+ * is what the render sees too.
839
+ *
840
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
841
+ */
842
+ resolveAsync(key: string, ...args: unknown[]): Promise<unknown>;
843
+ }
844
+
665
845
  /**
666
846
  * Global style (CSS rule) shapes and the `etch.styles` API surface.
667
847
  */
@@ -1461,6 +1641,16 @@ interface Etch {
1461
1641
  blocks: EtchBlocksApi;
1462
1642
  /** Loop definitions and binding loops to blocks. */
1463
1643
  loops: EtchLoopsApi;
1644
+ /**
1645
+ * Project data sources — the `dataSources` capability's model of dynamic
1646
+ * data, which supersedes {@link loops}.
1647
+ *
1648
+ * Optional: a runtime that does not back the capability omits the namespace
1649
+ * entirely rather than stubbing it, so check
1650
+ * `environment.capabilities.dataSources === true` (or the presence of this
1651
+ * property) before calling it.
1652
+ */
1653
+ dataSources?: EtchDataSourcesApi;
1464
1654
  /** Global style (CSS) definitions and CSS variables. */
1465
1655
  styles: EtchStylesApi;
1466
1656
  /** Global stylesheets and `@custom-media` definitions. */
@@ -1510,6 +1700,16 @@ interface Etch {
1510
1700
  readonly apiVersion: string;
1511
1701
  /** The Etch builder (product) version, for capability checks. */
1512
1702
  readonly version: string;
1703
+ /**
1704
+ * What this runtime is and which optional surfaces it backs. Read this
1705
+ * before using `loops`, `fields`, templates or WordPress media, and before
1706
+ * authoring a block type that may not exist here.
1707
+ *
1708
+ * Optional: runtimes older than the descriptor omit it. Treat a missing
1709
+ * `environment` as `etch-wp` on WordPress with everything available, which
1710
+ * is what every runtime that predates it was.
1711
+ */
1712
+ readonly environment?: EtchEnvironment;
1513
1713
  }
1514
1714
 
1515
1715
  declare global {
@@ -1555,7 +1755,7 @@ declare function getEtch(options?: ConnectOptions): Etch;
1555
1755
  * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for
1556
1756
  * the known values.
1557
1757
  */
1558
- type EtchApiErrorCode = 'BLOCK_NOT_FOUND' | 'WRONG_BLOCK_TYPE' | 'READONLY' | 'INVALID_ARGUMENT' | 'LOOP_NOT_FOUND' | 'STYLE_NOT_FOUND' | 'STYLESHEET_NOT_FOUND' | 'COMPONENT_NOT_FOUND' | 'POST_NOT_FOUND' | 'OPERATION_FAILED' | 'NOT_AVAILABLE' | (string & {});
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 & {});
1559
1759
  /** Error thrown by the public Etch API (and by this client). */
1560
1760
  declare class EtchApiError extends Error {
1561
1761
  readonly code: EtchApiErrorCode;
@@ -1574,4 +1774,4 @@ declare function isEtchApiError(value: unknown): value is EtchApiError;
1574
1774
  */
1575
1775
  declare const ETCH_API_VERSION = "0.x";
1576
1776
 
1577
- 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 EtchComponentBlockJson, type EtchComponentsApi, type EtchConditionBlockJson, type EtchDynamicElementBlockJson, type EtchDynamicImageBlockJson, type EtchElementBlockJson, type EtchFieldsApi, type EtchHistoryApi, type EtchHtmlAttributes, type EtchLoop, type EtchLoopBlockJson, type EtchLoopConfig, type EtchLoopObj, type EtchLoopsApi, type EtchNavigationApi, type EtchPassthroughBlockJson, type EtchPostContentBlockJson, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -340,15 +340,26 @@ interface EtchLoopBlockJson extends EtchBlockCommon {
340
340
  type: 'etch/loop';
341
341
  /** Variable name bound to the current item (e.g. `item`). */
342
342
  itemId: string;
343
- /** What the block iterates over (a dynamic path); omitted when bound via `loopId`. */
343
+ /**
344
+ * What the block iterates over.
345
+ *
346
+ * On a `dataSources` runtime this is the whole binding and is **required**:
347
+ * any dynamic expression yielding a list — `data('products').items`, an
348
+ * enclosing loop's `item.tags`, `range(1, 10)`. On a `loops` runtime it is
349
+ * optional and means a sub-path within the loop bound by `loopId`.
350
+ */
344
351
  target?: string;
345
352
  /** Variable name bound to the current index. */
346
353
  indexId?: string;
347
- /** Id of a registered loop definition this block is bound to. */
354
+ /**
355
+ * Id of a registered loop definition this block is bound to.
356
+ * **Requires the `loops` capability** — ignored where loops are not backed.
357
+ */
348
358
  loopId?: string;
349
359
  /**
350
360
  * Values for the bound loop's parameters. Every key must be a `$`-prefixed
351
361
  * parameter reference (e.g. `$count`).
362
+ * **Requires the `loops` capability** — ignored where loops are not backed.
352
363
  */
353
364
  loopParams?: Record<LoopParamRef, unknown>;
354
365
  }
@@ -390,7 +401,13 @@ interface EtchRawHtmlBlockJson extends EtchBlockCommon {
390
401
  /** The original, unsanitized HTML as authored. */
391
402
  unsafe: string;
392
403
  }
393
- /** A pass-through wrapper around a native Gutenberg block (`etch/passthrough`). */
404
+ /**
405
+ * A pass-through wrapper around a native Gutenberg block (`etch/passthrough`).
406
+ *
407
+ * **Not registered on every build** — this one is WordPress-only. Check
408
+ * `etch.environment.blockTypes` before authoring it; creating a block type the
409
+ * runtime does not register is rejected.
410
+ */
394
411
  interface EtchPassthroughBlockJson extends EtchBlockCommon {
395
412
  type: 'etch/passthrough';
396
413
  /** The wrapped Gutenberg block. */
@@ -402,8 +419,8 @@ interface EtchPassthroughBlockJson extends EtchBlockCommon {
402
419
  * time, such as a content-type field. Its children are the resolved JSON, so it
403
420
  * accepts no authored children.
404
421
  *
405
- * **Not registered on every build** — this one is Studio-only; creating a block
406
- * type the runtime does not register is rejected.
422
+ * **Not registered on every build** — this one is Studio-only. Check
423
+ * `etch.environment.blockTypes` before authoring it.
407
424
  */
408
425
  interface EtchRenderBlockJson extends EtchBlockCommon {
409
426
  type: 'etch/render';
@@ -484,9 +501,9 @@ interface BlockPatch {
484
501
  /** Replace the text content. Only valid on text blocks. */
485
502
  text?: string;
486
503
  /**
487
- * Re-point what the block resolves at: the expression an `etch/loop` block
488
- * iterates over, or the one an `etch/render` block renders. Only valid on
489
- * block types that carry a `target`.
504
+ * Re-point what the block resolves at: the iterated expression of an
505
+ * `etch/loop` block, or the rendered expression of an `etch/render` block.
506
+ * Only valid on those two types.
490
507
  */
491
508
  target?: string;
492
509
  }
@@ -662,6 +679,169 @@ interface EtchBlocksApi {
662
679
  saveComponentEditModeAsync(): Promise<void>;
663
680
  }
664
681
 
682
+ /**
683
+ * What this Etch runtime actually is, and which optional surfaces it backs.
684
+ *
685
+ * Etch ships as more than one product against **one** contract: `etch-wp` runs
686
+ * inside WordPress, `etch-studio` is standalone. They expose the same `etch`
687
+ * namespaces, but a namespace can be declared and not implemented — those
688
+ * methods throw `EtchApiError` with code `NOT_AVAILABLE`.
689
+ *
690
+ * `etch.environment` is how a caller finds that out **without** calling and
691
+ * catching. It is deliberately a description of the runtime rather than of the
692
+ * product: prefer `capabilities.loops` over `product === 'etch-wp'`, because
693
+ * capabilities move between products over time (the data manager, for one, is
694
+ * on its way to WordPress) while a product check silently rots the day they do.
695
+ */
696
+ /** Which build of Etch is running. Identity — not a capability check. */
697
+ type EtchProduct = 'etch-wp' | 'etch-studio' | (string & {});
698
+ /**
699
+ * Optional surfaces a runtime may or may not back.
700
+ *
701
+ * `loops` and `dataSources` are the two dynamic-data models, and they decide
702
+ * how an `etch/loop` block is authored — check which one you are on before
703
+ * writing one:
704
+ *
705
+ * - `loops` — a registry of named loop definitions. A loop block binds to one
706
+ * by `loopId` (plus `$`-prefixed `loopParams`), and `target` is an optional
707
+ * sub-path within it.
708
+ * - `dataSources` — named, typed project sources (`json`, `api`, `js`), reached
709
+ * through a `data('key')` expression. A loop block carries **only** a
710
+ * required `target`, and that target is any *dynamic expression* yielding a
711
+ * list — a data source, a path into one (`data('products').items`), nested
712
+ * data from an enclosing loop (`item.tags`), or a generated list
713
+ * (`range(1, 10)`). There is no `loopId`/`loopParams`. This model supersedes
714
+ * `loops` and is expected on both products eventually.
715
+ *
716
+ * The rest:
717
+ *
718
+ * - `fields` — `etch.fields.*` is backed by a real field store.
719
+ * - `templates` — template posts exist and `navigation.goTo('templates')` works.
720
+ * - `wpMedia` — media ids resolve against the **WordPress media library**, so a
721
+ * URL can be turned into an id through `/wp/v2/media`. Without it the ids are
722
+ * the product's own asset store instead. Either way an `etch/dynamic-image`
723
+ * block binds the same: a numeric `mediaId` attribute.
724
+ */
725
+ type EtchCapability = 'loops' | 'dataSources' | 'fields' | 'templates' | 'wpMedia';
726
+ /**
727
+ * Capability map. Every key is optional on purpose: a runtime older than a
728
+ * capability's introduction simply omits it, so **absent means unavailable**.
729
+ * Test with `=== true` rather than truthiness of a possibly-missing key.
730
+ */
731
+ type EtchCapabilities = Readonly<Partial<Record<EtchCapability, boolean>>>;
732
+ /** Runtime self-description, exposed as `etch.environment`. */
733
+ interface EtchEnvironment {
734
+ /**
735
+ * Which build this is — identity, for display and diagnostics. Every
736
+ * behavioral decision belongs to {@link capabilities} instead; a product
737
+ * check goes wrong the moment a capability moves between products.
738
+ */
739
+ readonly product: EtchProduct;
740
+ /** Which optional surfaces this runtime actually backs. */
741
+ readonly capabilities: EtchCapabilities;
742
+ /**
743
+ * Every block `type` this runtime can construct, sorted. Read this instead
744
+ * of assuming a block exists: `etch/passthrough` is WordPress-only and
745
+ * `etch/render` is Studio-only, and the list grows with each release.
746
+ */
747
+ readonly blockTypes: readonly string[];
748
+ }
749
+
750
+ /**
751
+ * Project data sources and the `etch.dataSources` API surface.
752
+ *
753
+ * Data sources are the `dataSources` capability's model of dynamic data, and
754
+ * they supersede {@link EtchLoopsApi | loops}: a named, typed source is defined
755
+ * once for the project and read anywhere an expression is accepted, through
756
+ * `data('key')`. A loop block points its `target` at one of those expressions
757
+ * rather than binding to a loop id.
758
+ *
759
+ * Check `etch.environment.capabilities.dataSources === true` before using this
760
+ * namespace — a runtime without it omits the whole thing.
761
+ */
762
+ /**
763
+ * A source type. The three built-ins are listed; the registry behind the
764
+ * runtime is open, so a host may serve types this contract has never heard of.
765
+ */
766
+ type EtchDataSourceType = 'json' | 'api' | 'js' | (string & {});
767
+ /**
768
+ * A project data source, identified by its `key` — the name `data('key')`
769
+ * calls it by.
770
+ */
771
+ interface EtchDataSource {
772
+ /**
773
+ * Stable identifier used in expressions: `data('products')`. Unique within
774
+ * the project, and part of every authored expression that reads the source,
775
+ * so renaming one breaks those expressions.
776
+ */
777
+ key: string;
778
+ /** Display name shown in the data manager. */
779
+ name: string;
780
+ /** Which kind of source this is; decides how `metadata` is read. */
781
+ type: EtchDataSourceType;
782
+ /**
783
+ * Type-specific configuration. The built-in types read:
784
+ *
785
+ * - `json` — `{ data: unknown }`, the payload served verbatim.
786
+ * - `api` — `{ url: string; method?: string; headers?: [string, string][] }`,
787
+ * fetched and parsed as JSON. A non-OK response fails the source.
788
+ * - `js` — `{ code: string }`, a function body run in a sandbox. Arguments
789
+ * passed by the expression (`data('rows', 5)`) arrive as `args`.
790
+ */
791
+ metadata: Record<string, unknown>;
792
+ }
793
+ /** Fields of an {@link EtchDataSource} that `updateAsync` can change. */
794
+ type EtchDataSourcePatch = Partial<EtchDataSource>;
795
+ /**
796
+ * Project data sources: definitions, and resolving one to its value.
797
+ *
798
+ * Reads are synchronous — the project's sources are loaded before `window.etch`
799
+ * exists. Writes persist to the backend immediately (they do not wait for
800
+ * {@link Etch.saveAsync}) and drop any value already resolved from the source,
801
+ * so the canvas re-reads it.
802
+ */
803
+ interface EtchDataSourcesApi {
804
+ /** Every data source defined for this project. */
805
+ list(): EtchDataSource[];
806
+ /**
807
+ * One data source by key.
808
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
809
+ */
810
+ get(key: string): EtchDataSource;
811
+ /**
812
+ * Fuzzy-search data sources by `name` or `key`, ranked best-first. Returns
813
+ * an empty array for a blank query or when nothing matches.
814
+ */
815
+ findDataSource(query: string): EtchDataSource[];
816
+ /**
817
+ * Define a new data source.
818
+ * @throws {EtchApiError} `INVALID_ARGUMENT` on a malformed source, or when
819
+ * the key is already taken.
820
+ */
821
+ createAsync(source: EtchDataSource): Promise<EtchDataSource>;
822
+ /**
823
+ * Change an existing data source. Only the fields present are applied.
824
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
825
+ */
826
+ updateAsync(key: string, patch: EtchDataSourcePatch): Promise<EtchDataSource>;
827
+ /**
828
+ * Delete a data source. Expressions still naming it resolve to `undefined`.
829
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
830
+ */
831
+ deleteAsync(key: string): Promise<void>;
832
+ /**
833
+ * Resolve a data source to its value, exactly as `data('key', ...args)`
834
+ * would on the canvas — read this before authoring the property paths that
835
+ * depend on its shape.
836
+ *
837
+ * A source that fails resolves to `undefined` rather than rejecting, which
838
+ * is what the render sees too.
839
+ *
840
+ * @throws {EtchApiError} `DATA_SOURCE_NOT_FOUND`
841
+ */
842
+ resolveAsync(key: string, ...args: unknown[]): Promise<unknown>;
843
+ }
844
+
665
845
  /**
666
846
  * Global style (CSS rule) shapes and the `etch.styles` API surface.
667
847
  */
@@ -1461,6 +1641,16 @@ interface Etch {
1461
1641
  blocks: EtchBlocksApi;
1462
1642
  /** Loop definitions and binding loops to blocks. */
1463
1643
  loops: EtchLoopsApi;
1644
+ /**
1645
+ * Project data sources — the `dataSources` capability's model of dynamic
1646
+ * data, which supersedes {@link loops}.
1647
+ *
1648
+ * Optional: a runtime that does not back the capability omits the namespace
1649
+ * entirely rather than stubbing it, so check
1650
+ * `environment.capabilities.dataSources === true` (or the presence of this
1651
+ * property) before calling it.
1652
+ */
1653
+ dataSources?: EtchDataSourcesApi;
1464
1654
  /** Global style (CSS) definitions and CSS variables. */
1465
1655
  styles: EtchStylesApi;
1466
1656
  /** Global stylesheets and `@custom-media` definitions. */
@@ -1510,6 +1700,16 @@ interface Etch {
1510
1700
  readonly apiVersion: string;
1511
1701
  /** The Etch builder (product) version, for capability checks. */
1512
1702
  readonly version: string;
1703
+ /**
1704
+ * What this runtime is and which optional surfaces it backs. Read this
1705
+ * before using `loops`, `fields`, templates or WordPress media, and before
1706
+ * authoring a block type that may not exist here.
1707
+ *
1708
+ * Optional: runtimes older than the descriptor omit it. Treat a missing
1709
+ * `environment` as `etch-wp` on WordPress with everything available, which
1710
+ * is what every runtime that predates it was.
1711
+ */
1712
+ readonly environment?: EtchEnvironment;
1513
1713
  }
1514
1714
 
1515
1715
  declare global {
@@ -1555,7 +1755,7 @@ declare function getEtch(options?: ConnectOptions): Etch;
1555
1755
  * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for
1556
1756
  * the known values.
1557
1757
  */
1558
- type EtchApiErrorCode = 'BLOCK_NOT_FOUND' | 'WRONG_BLOCK_TYPE' | 'READONLY' | 'INVALID_ARGUMENT' | 'LOOP_NOT_FOUND' | 'STYLE_NOT_FOUND' | 'STYLESHEET_NOT_FOUND' | 'COMPONENT_NOT_FOUND' | 'POST_NOT_FOUND' | 'OPERATION_FAILED' | 'NOT_AVAILABLE' | (string & {});
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 & {});
1559
1759
  /** Error thrown by the public Etch API (and by this client). */
1560
1760
  declare class EtchApiError extends Error {
1561
1761
  readonly code: EtchApiErrorCode;
@@ -1574,4 +1774,4 @@ declare function isEtchApiError(value: unknown): value is EtchApiError;
1574
1774
  */
1575
1775
  declare const ETCH_API_VERSION = "0.x";
1576
1776
 
1577
- 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 EtchComponentBlockJson, type EtchComponentsApi, type EtchConditionBlockJson, type EtchDynamicElementBlockJson, type EtchDynamicImageBlockJson, type EtchElementBlockJson, type EtchFieldsApi, type EtchHistoryApi, type EtchHtmlAttributes, type EtchLoop, type EtchLoopBlockJson, type EtchLoopConfig, type EtchLoopObj, type EtchLoopsApi, type EtchNavigationApi, type EtchPassthroughBlockJson, type EtchPostContentBlockJson, 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 };
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 };
package/dist/index.js.map CHANGED
@@ -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,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.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| '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":";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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-gravy/etch-public-api",
3
- "version": "0.9.0",
3
+ "version": "0.11.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": {