@paged-media/plugin-api 0.2.43-canary.0 → 0.2.46-canary.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/dist/host.d.ts CHANGED
@@ -8,6 +8,7 @@ import type { MutationInput } from "./mutations";
8
8
  import type { PluginManifest } from "./manifest";
9
9
  import type { SchemaPanelContribution } from "./panel-schema";
10
10
  import type { WidgetSurface } from "./widgets";
11
+ import type { ObjectModelContribution, ObjectModelHandle, ObjectsSurface } from "./object-model";
11
12
  /** Everything a bundle registers is disposable; the host ALSO tracks
12
13
  * it, so deactivation teardown is structural, not conventional. */
13
14
  export interface Disposable {
@@ -474,6 +475,21 @@ export interface ContributionSurface {
474
475
  * layer it submitted.
475
476
  */
476
477
  sceneLayer(): SceneLayerSurface;
478
+ /**
479
+ * Contribute this bundle's OBJECT MODEL (ADR 323, DESIGN.md §21): its
480
+ * object kinds — typed schema rows plus `list` / `get` / `set` / `batch`
481
+ * handlers — and typed commands. Every kind becomes addressable as
482
+ * `plugin:<manifest.id>/<kind>/<id>` through `host.objects` (and so from
483
+ * bindings, Boa, the Node CLI and schema-driven panel fields), headless
484
+ * and outside any edit context.
485
+ *
486
+ * Capability-gated: every kind must be declared in
487
+ * `contributes.objectModel.kinds[]` and every command in
488
+ * `contributes.objectModel.commands[]`; a kind with no schema rows, a row
489
+ * whose type is not a `ValueType`, or a duplicate path is refused loudly.
490
+ * Probe `supports("contribute.objectModel@1")`.
491
+ */
492
+ objectModel(model: ObjectModelContribution): ObjectModelHandle;
477
493
  }
478
494
  /** The scene-layer surface (C-1) returned by `contribute.sceneLayer()`.
479
495
  * `elementId` is the host `Self` id of the frame to render into. */
@@ -726,6 +742,11 @@ export type MutationOutcome = {
726
742
  createdId: ElementId | null;
727
743
  pageIds: PageId[];
728
744
  minted?: MintedElement[];
745
+ /** The engine's sequence number of this write (the wire's
746
+ * `mutationApplied.appliedSeq`), when the host knows it. A script
747
+ * host bridge reports it back as `HostObjectsResult.committed` so
748
+ * the write folds into the script's undo step (protocol 71). */
749
+ appliedSeq?: number;
729
750
  } | {
730
751
  applied: false;
731
752
  error: unknown;
@@ -1829,6 +1850,13 @@ export interface BundleHost {
1829
1850
  * subscription. Always present; `supports("tools.settings@1")`
1830
1851
  * reports whether a host store is wired. */
1831
1852
  readonly tools: ToolsSurface;
1853
+ /** The OBJECT registry (ADR 323): one `query` / `get` / `set` / `batch` /
1854
+ * `schema` over core addresses and every plugin's contributed kinds.
1855
+ * Always present. Reads are gated on `capabilities.document.read`,
1856
+ * writes on `capabilities.document.write` (a refused write answers
1857
+ * `{ applied: false }`). `supports("objects@1")`; cross-plugin routing
1858
+ * (a SHARED registry the host injected) is `supports("objects.shared@1")`. */
1859
+ readonly objects: ObjectsSurface;
1832
1860
  /** Capability detection over version sniffing: feature strings of
1833
1861
  * the form `"area.member@major"` (see HOST_FEATURES in plugin-sdk). */
1834
1862
  supports(feature: string): boolean;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export type { PluginId, PluginManifest, PluginCapabilities, PluginContributions, NetworkCapability, DataProvidersCapability, StorageCapability, WasmArtifact, WasmPurpose, WorkersCapability, SecretsCapability, DocumentsCapability, GpuCapability, } from "./manifest";
1
+ export type { PluginId, PluginManifest, PluginCapabilities, PluginContributions, NetworkCapability, DataProvidersCapability, StorageCapability, WasmArtifact, WasmPurpose, WorkersCapability, SecretsCapability, DocumentsCapability, GpuCapability, ObjectModelDeclaration, } from "./manifest";
2
+ export type { Address, Selector, PropertyPathName, PageItemKind, StyleFamily, ParsedAddress, AddressKind, ValueType, StructField, ValueTypeKind, PropertySchema, ContentSchema, ObjectErrorCode, ObjectValue, ObjectOp, PartChange, ObjectWrite, ObjectQuery, ObjectPredicate, ObjectKindContribution, TypedCommandContribution, ObjectModelContribution, ObjectModelHandle, ObjectWriteResult, ObjectCommandResult, ObjectChange, ObjectKindInfo, ObjectCommandInfo, ObjectsSurface, ListEdit, StructEdit, ValueEdit, } from "./object-model";
2
3
  export type { BundleHandle, PagedBundle } from "./bundle";
3
4
  export type { BundleHost, ContributionSurface, SceneLayerSurface, SceneLayerSubmitResult, SceneImage, SceneImageTile, SceneImageSubmitOptions, WillSaveEvent, ToolsSurface, ToolSettingValue, ImagesSurface, ImageResourceClaimOptions, TileBytes, WorkersSurface, BundleWorker, SpawnWorkerOptions, SecretsSurface, SecretMaterial, DocumentSurface, MutateWithBytesOptions, SelectionSurface, ViewportSurface, RenderSurface, RenderSnapshotOptions, RenderedPage, TextSurface, TextMetrics, TextCaret, FrameChainLink, PlanarFace, PlanarRegionsResult, StoryContent, ParagraphContent, RunContent, OverlaySurface, OverlayLayer, ShellSurface, FilePickerOptions, PickedFile, SaveFileOptions, StorageSurface, BlobSurface, BlobUsage, PartsSurface, NativeDocumentSurface, DocumentsSurface, OpenDocumentOptions, OpenDocumentResult, NetworkSurface, ConsentResult, DataProvidersSurface, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, ProviderSchema, ProviderField, ProviderRecordSet, DiagnosticsSurface, JournalSurface, JournalRecord, BindingsSurface, Diagnostic, DocumentChangeEvent, DocumentOpenedEvent, MutationOutcome, Disposable, PluginLogger, EditContextContribution, ObjectTypeContribution, EditContextCandidate, EnteredEditContext, ContentPointerEvent, ContentWheelEvent, EditContextDescriptor, ObjectTypeDescriptor, PluginMetadataEnvelope, ObjectTypeBaker, BakeContext, MenuContribution, } from "./host";
4
- export type { PanelSchema, PanelSchemaSection, PanelSchemaRow, SchemaPanelContribution, SchemaPanelRenderer, SchemaPanelRendererProps, WidgetValueBinding, BindingRef, SchemaGate, WidgetCollectionBinding, SchemaRowAction, SchemaListAction, SchemaListSpec, } from "./panel-schema";
5
- export type { WidgetSurface, ColorPickerProps, CodeEditorProps, CodeEditorDiagnostic, CodeEditorLanguage, } from "./widgets";
5
+ export type { PanelSchema, PanelSchemaSection, PanelSchemaRow, SchemaPanelContribution, SchemaPanelRenderer, SchemaPanelRendererProps, WidgetValueBinding, BindingRef, SchemaGate, WidgetCollectionBinding, SchemaRowAction, SchemaListAction, SchemaListSpec, PanelSchemaWidgetRow, PanelSchemaPropertyRow, PropertyFieldAddress, PropertyFieldStyle, PropertyFieldOption, PropertySubField, PropertyListPresentation, } from "./panel-schema";
6
+ export type { WidgetSurface, ColorPickerProps, CodeEditorProps, CodeEditorDiagnostic, CodeEditorLanguage, PropertyFieldProps, PropertyFrameProps, PropertyFieldTarget, } from "./widgets";
6
7
  export type { BindingTarget, BindingPropertyRequest, BindingPropertyWrite, BindingCollectionRequest, BindingRead, BindingResolved, BindingDecline, BindingWrite, BindingCollection, BindingOp, BindingProvider, BindingProviderScope, BindingProviderHandle, BindingReadResult, BindingWriteResult, BindingCollectionResult, ActiveBindingProvider, } from "./binding-provider";
7
8
  export type { AssetSurface, AssetKind, FontFaceAsset, FontFaceFormat, } from "./assets";
8
9
  export type { ProviderTileWire, SnapshotPng } from "./wire";
@@ -2,6 +2,7 @@ import type { AssetKind } from "./assets";
2
2
  /** Reverse-DNS plugin identity, e.g. `media.paged.draw`. Doubles as
3
3
  * the namespace prefix for every contribution id the bundle
4
4
  * registers (`media.paged.draw.tool.pen`). */
5
+ import type { PropertySchema, ValueType } from "./object-model";
5
6
  export type PluginId = string;
6
7
  export interface PluginManifest {
7
8
  id: PluginId;
@@ -337,6 +338,14 @@ export interface PluginContributions {
337
338
  * WITHOUT central blessing of each data type. The bytes are read/written
338
339
  * through the `host.parts` door into the plugin's own `paged/<plugin-id>/`
339
340
  * namespace. Purely DECLARATIVE — no runtime behaviour rides on this. */
341
+ /** ADR 323 — the OBJECT MODEL this bundle contributes through
342
+ * `host.contribute.objectModel`. Declaring it is the capability gate: a
343
+ * kind or typed command registered at runtime must be listed here. Each
344
+ * `schema` / `args` is either the rows / value type INLINE or a
345
+ * bundle-relative path to a JSON file holding them (plugin-cli reads
346
+ * it). `plugin-cli validate` fails a kind with no schema rows, a row
347
+ * whose type is not a `ValueType`, and a command with no `args`. */
348
+ objectModel?: ObjectModelDeclaration;
340
349
  partTypes?: Array<{
341
350
  /** A plugin-local type name (e.g. "sheet", "imageStack", "barcode"). */
342
351
  type: string;
@@ -351,3 +360,19 @@ export interface PluginContributions {
351
360
  linkable?: boolean;
352
361
  }>;
353
362
  }
363
+ /** `contributes.objectModel` (ADR 323). */
364
+ export interface ObjectModelDeclaration {
365
+ kinds: Array<{
366
+ kind: string;
367
+ title: string;
368
+ /** Inline schema rows, or a bundle-relative path to a JSON array. */
369
+ schema: string | readonly PropertySchema[];
370
+ }>;
371
+ commands?: Array<{
372
+ id: string;
373
+ title: string;
374
+ /** Inline value type, or a bundle-relative path to a JSON file. */
375
+ args: string | ValueType;
376
+ result?: string | ValueType;
377
+ }>;
378
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Mutation } from "./wire";
2
- export type { ElementId, PageId, NodeId, NodeSpec, Mutation, Operation, PropertyPath, Value, PathAnchorSpec, PathAnchorTriple, PathAnchorsResult, PathPointAddress, PathPointRole, PathfinderKind, HitFilter, HitResult, CollectionName, DocumentMeta, ElementGeometryItem, SceneTreeNode, SelectionMode, ContentSelection, MainToWorker, MainToWorkerKind, WorkerToMain, SceneLayer, SceneItem, ScenePathSeg, ScenePaint, GestureType, GestureHandle, GestureModifiers, SwatchSpec, GradientSpec, GradientStopSpec, SwatchSummary, GradientSummary, LayerSummary, } from "./wire";
2
+ export type { ElementId, PageId, NodeId, NodeSpec, Mutation, Operation, PropertyPath, Value, PathAnchorSpec, PathAnchorTriple, PathAnchorsResult, PathPointAddress, PathPointRole, PathfinderKind, HitFilter, HitResult, CollectionName, DocumentMeta, ElementGeometryItem, SceneTreeNode, SelectionMode, ContentSelection, MainToWorker, MainToWorkerKind, WorkerToMain, SceneLayer, SceneItem, ScenePathSeg, ScenePaint, GestureType, GestureHandle, GestureModifiers, SwatchSpec, GradientSpec, GradientStopSpec, SwatchSummary, GradientSummary, LayerSummary, PageSummary, StorySummary, } from "./wire";
3
3
  /** v56 (Wave B) — close an OPEN subpath of a path element: the inverse
4
4
  * gesture of `pathOpenAt`'s scissors cut. `subpath` picks the contour
5
5
  * by index; omit for the default (the single/last open subpath).
@@ -0,0 +1,560 @@
1
+ import type { MutationInput } from "./mutations";
2
+ import type { Disposable, MutationOutcome } from "./host";
3
+ /**
4
+ * One object's address (ADR 131). A SESSION HANDLE, not a durable
5
+ * reference: InDesign renumbers every `Self` on save, so anything stored in
6
+ * a document (a binding target) is a selector over a stable key — a `Name`
7
+ * or an `x-paged:` label — never a raw id.
8
+ *
9
+ * Canonical core forms (what the formatter emits):
10
+ * `doc` · `textFrame:<id>` (and `rectangle:` `oval:` `polygon:`
11
+ * `graphicLine:` `group:`) · `page:<id>` · `page:#<n>` · `spread:<id>` ·
12
+ * `layer:<id>` · `master:<id>` · `story:<id>` ·
13
+ * `storyRange:<id>@<start>..<end>` · `field:<story>@<offset>` ·
14
+ * `table:<id>` · `cell:<table>/<row>,<col>` · `style:<family>/<rest>` ·
15
+ * `swatch:<rest>` · `gradient:<rest>` · `colorGroup:<rest>` ·
16
+ * `numberingList:<rest>` · `guide:<id>` · `condition:<rest>` ·
17
+ * `conditionSet:<rest>` · `section:<id>` · `hyperlink:<id>` ·
18
+ * `textVariable:<id>` (protocol 71) ·
19
+ * `master:<id> > <item address>`
20
+ * Aliases that parse and are never emitted: `frame:<id>`,
21
+ * `story:<id>@a..b`, `textframe`, `rect`, `graphicline`, `storyrange`.
22
+ * Plugin objects: `plugin:<pluginId>/<kind>/<rest>`. Batch handles:
23
+ * `$h:<name>`.
24
+ */
25
+ export type Address = string;
26
+ /** ADR 131 selector: `step *( (" > " | " ") step )`, a step being an
27
+ * address, a kind or `*` followed by predicates — `[key op value]` with
28
+ * `= != ^= *=`, keys `name` `label` `label.<key>` `kind` `layer` `style`
29
+ * or any property path, and `:nth(n)`. */
30
+ export type Selector = string;
31
+ /** A property path: `frameStrokeWeight`, `name`, `points[3].anchor`,
32
+ * `labels[x-paged:oid]`. The reserved path `"content"` reads/writes an
33
+ * object's content (its `ContentSchema`). */
34
+ export type PropertyPathName = string;
35
+ /** The page-item kinds (core's `ElementId` string-id kinds). */
36
+ export type PageItemKind = "textFrame" | "rectangle" | "oval" | "polygon" | "graphicLine" | "group";
37
+ /** Style families a `style:` address names (= core's `StyleCollection`). */
38
+ export type StyleFamily = "paragraph" | "character" | "object" | "cell" | "table";
39
+ /** A parsed address — what the SDK's `parseAddress` returns and
40
+ * `formatAddress` takes. Plain data. */
41
+ export type ParsedAddress = {
42
+ kind: "doc";
43
+ } | {
44
+ kind: "item";
45
+ itemKind: PageItemKind;
46
+ id: string;
47
+ }
48
+ /** `frame:<id>` — kind-agnostic; the resolver finds the item kind. */
49
+ | {
50
+ kind: "frame";
51
+ id: string;
52
+ } | {
53
+ kind: "page";
54
+ id: string;
55
+ }
56
+ /** `page:#<n>` — the n-th page, 1-based, document order. */
57
+ | {
58
+ kind: "pageIndex";
59
+ index: number;
60
+ } | {
61
+ kind: "spread";
62
+ id: string;
63
+ } | {
64
+ kind: "layer";
65
+ id: string;
66
+ } | {
67
+ kind: "master";
68
+ id: string;
69
+ } | {
70
+ kind: "story";
71
+ id: string;
72
+ } | {
73
+ kind: "storyRange";
74
+ story: string;
75
+ start: number;
76
+ end: number;
77
+ } | {
78
+ kind: "field";
79
+ story: string;
80
+ offset: number;
81
+ } | {
82
+ kind: "table";
83
+ id: string;
84
+ } | {
85
+ kind: "cell";
86
+ table: string;
87
+ row: number;
88
+ col: number;
89
+ } | {
90
+ kind: "style";
91
+ family: StyleFamily;
92
+ name: string;
93
+ } | {
94
+ kind: "swatch" | "gradient" | "colorGroup" | "numberingList" | "condition" | "conditionSet";
95
+ name: string;
96
+ } | {
97
+ kind: "guide" | "section" | "hyperlink" | "textVariable";
98
+ id: string;
99
+ } | {
100
+ kind: "onMaster";
101
+ master: string;
102
+ item: ParsedAddress;
103
+ } | {
104
+ kind: "plugin";
105
+ plugin: string;
106
+ objectKind: string;
107
+ id: string;
108
+ } | {
109
+ kind: "handle";
110
+ name: string;
111
+ };
112
+ /** The ADR 131 address kind of a parsed address (`ParsedAddress` folds the
113
+ * six item kinds into `item` and the page index into `pageIndex`). */
114
+ export type AddressKind = ParsedAddress["kind"];
115
+ /** ADR 132's value types, mirrored verbatim. Value shapes:
116
+ * bool → boolean · number → number (in `unit`), or one of its
117
+ * `keywords` (a span count's `"All"`) · length → number (pt;
118
+ * unit strings like "3mm" coerce) · color → a swatch ref (`Color/Red`)
119
+ * · enum → one of `members` · text → string · ref → an address or the
120
+ * target's self id · bounds → [top,left,bottom,right] · transform →
121
+ * [a,b,c,d,tx,ty] · point → [x,y] · bytes → Uint8Array · asset → a
122
+ * `data:<mime>;base64,<payload>` URI or bare base64 (core reads it back
123
+ * as a `data:` URI typed by the bytes' magic; `host.objects` also takes
124
+ * a `Uint8Array` / `ArrayBuffer` and encodes it) · list → array · map →
125
+ * record keyed by string · struct → record. */
126
+ export type ValueType = {
127
+ kind: "bool";
128
+ } | {
129
+ kind: "number";
130
+ integer?: boolean;
131
+ unit?: "pt" | "deg" | "percent" | "em1000" | "ppi";
132
+ /** Words the row also takes in place of a number (core's catalog
133
+ * `members` on a number row: a span count's `"All"`). */
134
+ keywords?: readonly string[];
135
+ } | {
136
+ kind: "length";
137
+ } | {
138
+ kind: "color";
139
+ } | {
140
+ kind: "enum";
141
+ members: readonly string[];
142
+ } | {
143
+ kind: "text";
144
+ multiline?: boolean;
145
+ maxLength?: number;
146
+ } | {
147
+ kind: "ref";
148
+ to: string;
149
+ } | {
150
+ kind: "bounds";
151
+ } | {
152
+ kind: "transform";
153
+ } | {
154
+ kind: "point";
155
+ } | {
156
+ kind: "bytes";
157
+ mime?: readonly string[];
158
+ }
159
+ /** `shape` names the encoding core expects (`data:<mime>;base64,<payload>`). */
160
+ | {
161
+ kind: "asset";
162
+ shape?: string;
163
+ } | {
164
+ kind: "list";
165
+ of: ValueType;
166
+ maxItems?: number;
167
+ } | {
168
+ kind: "map";
169
+ of: ValueType;
170
+ keyPattern?: string;
171
+ }
172
+ /** `shape` names a core wire struct (`SwatchColorSpec`, `TabStopSpec`);
173
+ * `open: true` accepts fields `fields` does not list — core's catalog
174
+ * (protocol 71) names the shape but not its fields, and the engine
175
+ * validates them. */
176
+ | {
177
+ kind: "struct";
178
+ fields: Readonly<Record<string, StructField>>;
179
+ shape?: string;
180
+ open?: true;
181
+ };
182
+ export type ValueTypeKind = ValueType["kind"];
183
+ /**
184
+ * One field of a `struct`: a value type, plus whether a record may omit it.
185
+ * · `optional: true` — the field may be absent; nothing is filled in.
186
+ * · `default` — the field may be absent; `host.objects.invoke` (and a
187
+ * struct-typed `set`) fill the default in before the handler or the
188
+ * plugin sees the value. The default must satisfy the field's type
189
+ * (checked at registration).
190
+ * A field with neither is required.
191
+ */
192
+ export type StructField = ValueType & {
193
+ optional?: true;
194
+ default?: unknown;
195
+ };
196
+ /** ADR 132's schema ROW (the JSON shape the core catalog publishes, and
197
+ * the one plugins publish). One row per (address kind, path). */
198
+ export interface PropertySchema {
199
+ /** ADR 131 kind (`frame`, `swatch`, …); plugin rows: `plugin:<id>/<kind>`
200
+ * (the registry fills it in when a kind omits it). */
201
+ addressKind?: string;
202
+ /** Item kinds the row applies to; omitted = all. */
203
+ appliesTo?: readonly string[];
204
+ path: PropertyPathName;
205
+ type: ValueType;
206
+ /** `null` clears / inherits. */
207
+ nullable?: boolean;
208
+ range?: {
209
+ min?: number;
210
+ max?: number;
211
+ step?: number;
212
+ };
213
+ default?: unknown;
214
+ /** Default `readWrite`. `derived` is computed from other paths. */
215
+ access?: "readWrite" | "readOnly" | "derived";
216
+ /** Core only: the dedicated op a virtual path resolves to; `clearOp`
217
+ * names the op a `null` write resolves to when clearing is a DIFFERENT
218
+ * op (`nextTextFrame` → `unlinkFrames`, `opacityMask` →
219
+ * `releaseOpacityMask`). */
220
+ virtual?: {
221
+ op: string;
222
+ field?: string;
223
+ clearOp?: string;
224
+ } | null;
225
+ idml?: {
226
+ element: string;
227
+ attribute: string;
228
+ } | null;
229
+ /** Default: `access === "readWrite"`. */
230
+ bindable?: boolean;
231
+ since?: number;
232
+ summary?: string;
233
+ title?: string;
234
+ /** An expensive read (pixels, a whole sheet, story text): `getAll`
235
+ * skips it unless asked (`getAll(address, { content: true })`). The
236
+ * `content` row is always heavy. */
237
+ heavy?: boolean;
238
+ }
239
+ /** What an object's CONTENT is; drives content ops and `get … --content`. */
240
+ export interface ContentSchema {
241
+ kind: "text" | "rows" | "cells" | "pixels" | "vector" | "html" | "blob";
242
+ mime?: readonly string[];
243
+ }
244
+ /** Why a call was refused. Expected failures are results, never throws. */
245
+ export type ObjectErrorCode = "badAddress" | "unknownAddress" | "unknownPath" | "unknownCommand" | "invalidValue" | "readOnly"
246
+ /** In the grammar, but no backend routes it yet (core forms whose write
247
+ * lands with core's `Set`, ADR 132). */
248
+ | "unmapped" | "denied" | "failed";
249
+ /** One read (ADR 323). `refused` is this SDK's addition: an unknown
250
+ * address, a bad path or an unmapped kind answer a typed reason rather
251
+ * than an `absent` that would read as "the path does not apply". */
252
+ export type ObjectValue = {
253
+ kind: "value";
254
+ value: unknown;
255
+ } | {
256
+ kind: "mixed";
257
+ } | {
258
+ kind: "absent";
259
+ } | {
260
+ kind: "refused";
261
+ code: ObjectErrorCode;
262
+ reason: string;
263
+ };
264
+ /** One op of a batch (ADR 323). */
265
+ export type ObjectOp = {
266
+ op: "set";
267
+ address: Address;
268
+ path: PropertyPathName;
269
+ value: unknown;
270
+ } | {
271
+ op: "create";
272
+ kind: string;
273
+ within?: Address;
274
+ props?: Record<string, unknown>;
275
+ handle?: string;
276
+ } | {
277
+ op: "delete";
278
+ address: Address;
279
+ } | {
280
+ op: "invoke";
281
+ command: string;
282
+ args: unknown;
283
+ };
284
+ /**
285
+ * An edit of a LIST value (ADR 323 property rows; DESIGN.md §12.8): what a
286
+ * host's list field does when the user adds, removes, reorders or edits
287
+ * one item. It is not a new batch op: `planValueEdit` / `editValue`
288
+ * (plugin-sdk) read the current value and LOWER the edit to plain `set`
289
+ * ops — the whole new list on the list's own path, or, when the kind only
290
+ * types its items (`path[].field` rows, no `path` row), `set path[i].field`
291
+ * — so it lands as ONE batch = ONE undo step, through the same validation
292
+ * as any write, and every planner keeps receiving `set` / `create` /
293
+ * `delete` only.
294
+ * · `add` — insert `item` (absent: the item type's default) at `index`
295
+ * (absent: the end).
296
+ * · `remove` — drop the item at `index`.
297
+ * · `move` — move the item at `from` so it ends up at `to`.
298
+ * · `itemSet` — replace the item at `index`, or with `field` one field of
299
+ * a struct item.
300
+ * Indices are positions in the VALUE (a stack painted bottom → top is
301
+ * still indexed bottom-first, whatever order the UI lists it in).
302
+ */
303
+ export type ListEdit = {
304
+ edit: "add";
305
+ index?: number;
306
+ item?: unknown;
307
+ } | {
308
+ edit: "remove";
309
+ index: number;
310
+ } | {
311
+ edit: "move";
312
+ from: number;
313
+ to: number;
314
+ } | {
315
+ edit: "itemSet";
316
+ index: number;
317
+ field?: string;
318
+ value: unknown;
319
+ };
320
+ /** An edit of one field of a STRUCT value (a gradient stop, an anchor
321
+ * point): lowered like {@link ListEdit} to a `set` of the whole struct,
322
+ * or `set path.field` when the kind types the field as its own row. */
323
+ export type StructEdit = {
324
+ edit: "fieldSet";
325
+ field: string;
326
+ value: unknown;
327
+ };
328
+ /** One edit of a list or struct value. */
329
+ export type ValueEdit = ListEdit | StructEdit;
330
+ /** A container part change, in the WRITER's own `paged/<plugin-id>/`
331
+ * namespace (relative path; `bytes: null` deletes). */
332
+ export interface PartChange {
333
+ path: string;
334
+ bytes: Uint8Array | null;
335
+ }
336
+ /**
337
+ * What a plugin write produces; the host commits it as ONE undo step.
338
+ * · `mutations` — core-backed state: folded into the batch.
339
+ * · `state` — plugin-backed state (the label-hash pattern): the parts
340
+ * are written first (content-addressed, never overwritten), then the
341
+ * plugin's label is set on `host` INSIDE the batch. Undo reverts the
342
+ * label; the plugin reloads the state the label points back to.
343
+ *
344
+ * `host` is a page item, a story (range), a page (`page:<id>`,
345
+ * `page:#n` — protocol 70 `setPageMetadata`) or the document (`doc` —
346
+ * `setDocumentMetadata`).
347
+ *
348
+ * Core holds ONE label per plugin per host, under exactly
349
+ * `x-paged:<pluginId>`, whose value is the envelope
350
+ * `{"v": <int >= 1>, "data": {…}}` (≤ 64 KiB). So `labelKey` is either
351
+ * - `x-paged:<pluginId>` — `labelValue` IS that whole envelope
352
+ * (JSON text), and replaces the label; or
353
+ * - `x-paged:<pluginId>.<sub>` — a SUB-KEY: the registry reads the
354
+ * current envelope, sets `data[<sub>]` to `labelValue` (parsed
355
+ * when it is JSON text, else kept as the string) and writes the
356
+ * merged envelope back. Several sub-keys on one host — in one
357
+ * write or across writes of one batch — merge into one label.
358
+ * Any other key is refused before anything applies.
359
+ * · `both` — the two together.
360
+ * · `rejected` — a typed no.
361
+ *
362
+ * `created` — the address of each object the write's `create` ops make,
363
+ * ONE per `create` op the planner received, in the order received (the
364
+ * registry splices them into `ObjectWriteResult.created` at the ops'
365
+ * batch positions). When the engine mints the id, write the placeholder
366
+ * `$h:<handle>` (the `bindCreated` handle of that insert) or `$created`
367
+ * (the batch's `createdId`) inside the address; the registry replaces it
368
+ * with the minted element's id after the commit. Omitted: each of those
369
+ * creates answers `""`. A list of the wrong length is refused (`failed`)
370
+ * before anything applies.
371
+ */
372
+ export type ObjectWrite = {
373
+ kind: "mutations";
374
+ mutations: readonly MutationInput[];
375
+ created?: readonly Address[];
376
+ } | {
377
+ kind: "state";
378
+ parts: readonly PartChange[];
379
+ host: Address;
380
+ labelKey: string;
381
+ labelValue: string;
382
+ created?: readonly Address[];
383
+ } | {
384
+ kind: "both";
385
+ mutations: readonly MutationInput[];
386
+ parts: readonly PartChange[];
387
+ host: Address;
388
+ labelKey: string;
389
+ labelValue: string;
390
+ created?: readonly Address[];
391
+ } | {
392
+ kind: "rejected";
393
+ reason: string;
394
+ };
395
+ /** What a kind's `list` receives. The registry ALSO post-filters the
396
+ * selector's predicates by reading paths through `get`, so honouring
397
+ * `predicates` in `list` is an optimisation, never a duty. */
398
+ export interface ObjectQuery {
399
+ /** The selector step that selected this kind (`plugin:x/layer[name="a"]`). */
400
+ selector?: Selector;
401
+ kind?: string;
402
+ /** The container the selector scoped the step to (`scope > kind`):
403
+ * a page item or story (a host), or one of this plugin's own
404
+ * addresses (`plugin:x/image/1 > plugin:x/layer`). Container scopes
405
+ * (`doc`, `page:`, `spread:`, `master:`, `layer:`) never reach `list`:
406
+ * the registry lists unscoped and keeps the objects whose `hostOf` lies
407
+ * in the container. */
408
+ within?: Address;
409
+ limit?: number;
410
+ /** The step's parsed predicates (this SDK's addition to ADR 323). */
411
+ predicates?: readonly ObjectPredicate[];
412
+ }
413
+ export interface ObjectPredicate {
414
+ key: string;
415
+ op: "=" | "!=" | "^=" | "*=";
416
+ value: string;
417
+ }
418
+ /** One object KIND a plugin contributes (ADR 323). Headless; live for the
419
+ * whole session; declared under `contributes.objectModel.kinds[]`. */
420
+ export interface ObjectKindContribution {
421
+ /** Plugin-local kind name (`layer`, `cell`). Addresses are
422
+ * `plugin:<manifest.id>/<kind>/<id>`. */
423
+ kind: string;
424
+ title: string;
425
+ /** At least one row; validated at registration. */
426
+ schema: readonly PropertySchema[];
427
+ content?: ContentSchema;
428
+ /** The core element hosting an object (frame / scene layer / page /
429
+ * `doc`): where its labels live (ADR 559), and how the registry scopes
430
+ * `page:x > plugin:…` selectors. May answer asynchronously. */
431
+ hostOf(address: Address): Address | null | Promise<Address | null>;
432
+ list(query: ObjectQuery): readonly Address[] | Promise<readonly Address[]>;
433
+ get(address: Address, path: PropertyPathName): ObjectValue | Promise<ObjectValue>;
434
+ set?(address: Address, path: PropertyPathName, value: unknown): ObjectWrite | Promise<ObjectWrite>;
435
+ /** Preferred over repeated `set`: many ops, one write, one undo step.
436
+ * Receives this kind's `set` / `create` / `delete` ops. Ignored when
437
+ * the contribution has a plugin-level `batch`. */
438
+ batch?(ops: readonly ObjectOp[]): ObjectWrite | Promise<ObjectWrite>;
439
+ }
440
+ /**
441
+ * A TYPED command (ADR 323): its args and result carry value types, so the
442
+ * CLI, Boa and a generated form can call it without knowing the plugin.
443
+ * `host.objects.invoke` validates `args` first. The untyped
444
+ * `contribute.command` stays for the migration (a shrink-only list).
445
+ */
446
+ export interface TypedCommandContribution<A = unknown, R = unknown> {
447
+ id: string;
448
+ title: string;
449
+ /** Usually `{ kind: "struct", fields }`. */
450
+ args: ValueType;
451
+ result?: ValueType;
452
+ handler(ctx: {
453
+ objects: ObjectsSurface;
454
+ }, args: A): R | Promise<R>;
455
+ }
456
+ export interface ObjectModelContribution {
457
+ kinds: readonly ObjectKindContribution[];
458
+ commands?: readonly TypedCommandContribution[];
459
+ /**
460
+ * PLUGIN-level planner: receives every `set` / `create` / `delete` op of
461
+ * a batch that targets ANY of this plugin's kinds, in batch order, and
462
+ * answers ONE write. Use it whenever two kinds share state (an image's
463
+ * layers and adjustments live in one stack): planned per kind, each
464
+ * kind would start from the same state and the second write would drop
465
+ * the first one's changes. When present, the kinds' own `batch`/`set`
466
+ * are not used for writes. Without it, ops are planned per kind, and
467
+ * two per-kind `state` writes landing on the same host are refused
468
+ * (`failed`, "conflicting state writes") rather than silently merged.
469
+ */
470
+ batch?(ops: readonly ObjectOp[]): ObjectWrite | Promise<ObjectWrite>;
471
+ }
472
+ /** What `contribute.objectModel` returns: a `Disposable` (ADR 323) that
473
+ * can also announce changes the engine does not see (wasm-held state). */
474
+ export interface ObjectModelHandle extends Disposable {
475
+ /** No argument = every object of this plugin may have changed. */
476
+ changed(addresses?: readonly Address[]): void;
477
+ }
478
+ export interface ObjectWriteResult {
479
+ applied: boolean;
480
+ reason?: string;
481
+ /** Engine undo steps the write took: a batch is ONE. */
482
+ undoSteps: 0 | 1;
483
+ /** This SDK's additions: the refusal code and the failing op. */
484
+ code?: ObjectErrorCode;
485
+ index?: number;
486
+ /** The engine outcome (minted ids for `create`/`$h:`). */
487
+ outcome?: MutationOutcome;
488
+ /** The address of every object a `create` op made, in op order (this
489
+ * SDK's addition; a core create needs engine protocol 71). */
490
+ created?: readonly Address[];
491
+ /** One entry per typed CORE command (`invoke` of `core.<op>`) the batch
492
+ * ran, in op order: its op index and the addresses of what it created
493
+ * (this SDK's addition, DESIGN.md §21.12). */
494
+ commands?: readonly ObjectCommandResult[];
495
+ }
496
+ /** What one typed core command of a batch made. */
497
+ export interface ObjectCommandResult {
498
+ index: number;
499
+ command: string;
500
+ created: readonly Address[];
501
+ }
502
+ export interface ObjectChange {
503
+ /** Empty = "anything may have changed" (core reports page-level damage
504
+ * today, not addresses). */
505
+ addresses: readonly Address[];
506
+ paths?: readonly string[];
507
+ origin: "core" | string;
508
+ }
509
+ export interface ObjectKindInfo {
510
+ owner: "core" | string;
511
+ /** Qualified: core kinds bare (`frame`), plugin kinds
512
+ * `plugin:<pluginId>/<kind>`. */
513
+ kind: string;
514
+ title: string;
515
+ /** The address form, e.g. `plugin:acme.notes/note/<id>`. */
516
+ addressForm: string;
517
+ }
518
+ export interface ObjectCommandInfo {
519
+ id: string;
520
+ title: string;
521
+ owner: string;
522
+ args: ValueType;
523
+ result?: ValueType;
524
+ }
525
+ /** `host.objects` — the one registry over core and every plugin. */
526
+ export interface ObjectsSurface {
527
+ query(selector: Selector, opts?: {
528
+ limit?: number;
529
+ }): Promise<Address[]>;
530
+ get(address: Address, path: PropertyPathName): Promise<ObjectValue>;
531
+ /** Every schema path of the object, read — except `heavy` rows (and
532
+ * `content`), which are read only with `{ content: true }`. */
533
+ getAll(address: Address, opts?: {
534
+ content?: boolean;
535
+ }): Promise<Record<string, ObjectValue>>;
536
+ set(address: Address, path: PropertyPathName, value: unknown, opts?: {
537
+ label?: string;
538
+ }): Promise<ObjectWriteResult>;
539
+ /** All-or-nothing: validated and planned before anything applies; core
540
+ * ops and plugin plans land as ONE engine batch = one undo step. */
541
+ batch(ops: readonly ObjectOp[], opts?: {
542
+ label?: string;
543
+ }): Promise<ObjectWriteResult>;
544
+ /** The rows of an address's kind, or of a qualified kind name. Empty
545
+ * when the kind is unknown. */
546
+ schema(addressOrKind: Address | string): Promise<readonly PropertySchema[]>;
547
+ kinds(): Promise<readonly ObjectKindInfo[]>;
548
+ /** Typed commands contributed through `contribute.objectModel`. */
549
+ commands(): Promise<readonly ObjectCommandInfo[]>;
550
+ /** Run a typed command. Rejects with an `Error` whose message starts
551
+ * with the refusal code (`unknownCommand: …`, `invalidValue: …`).
552
+ * A CORE command (`core.<op>`, listed by `commands()` with owner
553
+ * `"core"`) runs as ONE batch and answers `{ created, undoSteps }`;
554
+ * it also joins `batch` as an `invoke` op (one undo step with the
555
+ * batch's sets / creates / deletes). */
556
+ invoke(command: string, args: unknown): Promise<unknown>;
557
+ onDidChange(listener: (e: ObjectChange) => void, filter?: {
558
+ selector?: Selector;
559
+ }): Disposable;
560
+ }