@paged-media/plugin-api 0.2.43-canary.0 → 0.2.45-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,556 @@
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`) · length → number (pt;
117
+ * unit strings like "3mm" coerce) · color → a swatch ref (`Color/Red`)
118
+ * · enum → one of `members` · text → string · ref → an address or the
119
+ * target's self id · bounds → [top,left,bottom,right] · transform →
120
+ * [a,b,c,d,tx,ty] · point → [x,y] · bytes → Uint8Array · asset → a
121
+ * `data:<mime>;base64,<payload>` URI or bare base64 (core reads it back
122
+ * as a `data:` URI typed by the bytes' magic; `host.objects` also takes
123
+ * a `Uint8Array` / `ArrayBuffer` and encodes it) · list → array · map →
124
+ * record keyed by string · struct → record. */
125
+ export type ValueType = {
126
+ kind: "bool";
127
+ } | {
128
+ kind: "number";
129
+ integer?: boolean;
130
+ unit?: "pt" | "deg" | "percent" | "em1000" | "ppi";
131
+ } | {
132
+ kind: "length";
133
+ } | {
134
+ kind: "color";
135
+ } | {
136
+ kind: "enum";
137
+ members: readonly string[];
138
+ } | {
139
+ kind: "text";
140
+ multiline?: boolean;
141
+ maxLength?: number;
142
+ } | {
143
+ kind: "ref";
144
+ to: string;
145
+ } | {
146
+ kind: "bounds";
147
+ } | {
148
+ kind: "transform";
149
+ } | {
150
+ kind: "point";
151
+ } | {
152
+ kind: "bytes";
153
+ mime?: readonly string[];
154
+ }
155
+ /** `shape` names the encoding core expects (`data:<mime>;base64,<payload>`). */
156
+ | {
157
+ kind: "asset";
158
+ shape?: string;
159
+ } | {
160
+ kind: "list";
161
+ of: ValueType;
162
+ maxItems?: number;
163
+ } | {
164
+ kind: "map";
165
+ of: ValueType;
166
+ keyPattern?: string;
167
+ }
168
+ /** `shape` names a core wire struct (`SwatchColorSpec`, `TabStopSpec`);
169
+ * `open: true` accepts fields `fields` does not list — core's catalog
170
+ * (protocol 71) names the shape but not its fields, and the engine
171
+ * validates them. */
172
+ | {
173
+ kind: "struct";
174
+ fields: Readonly<Record<string, StructField>>;
175
+ shape?: string;
176
+ open?: true;
177
+ };
178
+ export type ValueTypeKind = ValueType["kind"];
179
+ /**
180
+ * One field of a `struct`: a value type, plus whether a record may omit it.
181
+ * · `optional: true` — the field may be absent; nothing is filled in.
182
+ * · `default` — the field may be absent; `host.objects.invoke` (and a
183
+ * struct-typed `set`) fill the default in before the handler or the
184
+ * plugin sees the value. The default must satisfy the field's type
185
+ * (checked at registration).
186
+ * A field with neither is required.
187
+ */
188
+ export type StructField = ValueType & {
189
+ optional?: true;
190
+ default?: unknown;
191
+ };
192
+ /** ADR 132's schema ROW (the JSON shape the core catalog publishes, and
193
+ * the one plugins publish). One row per (address kind, path). */
194
+ export interface PropertySchema {
195
+ /** ADR 131 kind (`frame`, `swatch`, …); plugin rows: `plugin:<id>/<kind>`
196
+ * (the registry fills it in when a kind omits it). */
197
+ addressKind?: string;
198
+ /** Item kinds the row applies to; omitted = all. */
199
+ appliesTo?: readonly string[];
200
+ path: PropertyPathName;
201
+ type: ValueType;
202
+ /** `null` clears / inherits. */
203
+ nullable?: boolean;
204
+ range?: {
205
+ min?: number;
206
+ max?: number;
207
+ step?: number;
208
+ };
209
+ default?: unknown;
210
+ /** Default `readWrite`. `derived` is computed from other paths. */
211
+ access?: "readWrite" | "readOnly" | "derived";
212
+ /** Core only: the dedicated op a virtual path resolves to; `clearOp`
213
+ * names the op a `null` write resolves to when clearing is a DIFFERENT
214
+ * op (`nextTextFrame` → `unlinkFrames`, `opacityMask` →
215
+ * `releaseOpacityMask`). */
216
+ virtual?: {
217
+ op: string;
218
+ field?: string;
219
+ clearOp?: string;
220
+ } | null;
221
+ idml?: {
222
+ element: string;
223
+ attribute: string;
224
+ } | null;
225
+ /** Default: `access === "readWrite"`. */
226
+ bindable?: boolean;
227
+ since?: number;
228
+ summary?: string;
229
+ title?: string;
230
+ /** An expensive read (pixels, a whole sheet, story text): `getAll`
231
+ * skips it unless asked (`getAll(address, { content: true })`). The
232
+ * `content` row is always heavy. */
233
+ heavy?: boolean;
234
+ }
235
+ /** What an object's CONTENT is; drives content ops and `get … --content`. */
236
+ export interface ContentSchema {
237
+ kind: "text" | "rows" | "cells" | "pixels" | "vector" | "html" | "blob";
238
+ mime?: readonly string[];
239
+ }
240
+ /** Why a call was refused. Expected failures are results, never throws. */
241
+ export type ObjectErrorCode = "badAddress" | "unknownAddress" | "unknownPath" | "unknownCommand" | "invalidValue" | "readOnly"
242
+ /** In the grammar, but no backend routes it yet (core forms whose write
243
+ * lands with core's `Set`, ADR 132). */
244
+ | "unmapped" | "denied" | "failed";
245
+ /** One read (ADR 323). `refused` is this SDK's addition: an unknown
246
+ * address, a bad path or an unmapped kind answer a typed reason rather
247
+ * than an `absent` that would read as "the path does not apply". */
248
+ export type ObjectValue = {
249
+ kind: "value";
250
+ value: unknown;
251
+ } | {
252
+ kind: "mixed";
253
+ } | {
254
+ kind: "absent";
255
+ } | {
256
+ kind: "refused";
257
+ code: ObjectErrorCode;
258
+ reason: string;
259
+ };
260
+ /** One op of a batch (ADR 323). */
261
+ export type ObjectOp = {
262
+ op: "set";
263
+ address: Address;
264
+ path: PropertyPathName;
265
+ value: unknown;
266
+ } | {
267
+ op: "create";
268
+ kind: string;
269
+ within?: Address;
270
+ props?: Record<string, unknown>;
271
+ handle?: string;
272
+ } | {
273
+ op: "delete";
274
+ address: Address;
275
+ } | {
276
+ op: "invoke";
277
+ command: string;
278
+ args: unknown;
279
+ };
280
+ /**
281
+ * An edit of a LIST value (ADR 323 property rows; DESIGN.md §12.8): what a
282
+ * host's list field does when the user adds, removes, reorders or edits
283
+ * one item. It is not a new batch op: `planValueEdit` / `editValue`
284
+ * (plugin-sdk) read the current value and LOWER the edit to plain `set`
285
+ * ops — the whole new list on the list's own path, or, when the kind only
286
+ * types its items (`path[].field` rows, no `path` row), `set path[i].field`
287
+ * — so it lands as ONE batch = ONE undo step, through the same validation
288
+ * as any write, and every planner keeps receiving `set` / `create` /
289
+ * `delete` only.
290
+ * · `add` — insert `item` (absent: the item type's default) at `index`
291
+ * (absent: the end).
292
+ * · `remove` — drop the item at `index`.
293
+ * · `move` — move the item at `from` so it ends up at `to`.
294
+ * · `itemSet` — replace the item at `index`, or with `field` one field of
295
+ * a struct item.
296
+ * Indices are positions in the VALUE (a stack painted bottom → top is
297
+ * still indexed bottom-first, whatever order the UI lists it in).
298
+ */
299
+ export type ListEdit = {
300
+ edit: "add";
301
+ index?: number;
302
+ item?: unknown;
303
+ } | {
304
+ edit: "remove";
305
+ index: number;
306
+ } | {
307
+ edit: "move";
308
+ from: number;
309
+ to: number;
310
+ } | {
311
+ edit: "itemSet";
312
+ index: number;
313
+ field?: string;
314
+ value: unknown;
315
+ };
316
+ /** An edit of one field of a STRUCT value (a gradient stop, an anchor
317
+ * point): lowered like {@link ListEdit} to a `set` of the whole struct,
318
+ * or `set path.field` when the kind types the field as its own row. */
319
+ export type StructEdit = {
320
+ edit: "fieldSet";
321
+ field: string;
322
+ value: unknown;
323
+ };
324
+ /** One edit of a list or struct value. */
325
+ export type ValueEdit = ListEdit | StructEdit;
326
+ /** A container part change, in the WRITER's own `paged/<plugin-id>/`
327
+ * namespace (relative path; `bytes: null` deletes). */
328
+ export interface PartChange {
329
+ path: string;
330
+ bytes: Uint8Array | null;
331
+ }
332
+ /**
333
+ * What a plugin write produces; the host commits it as ONE undo step.
334
+ * · `mutations` — core-backed state: folded into the batch.
335
+ * · `state` — plugin-backed state (the label-hash pattern): the parts
336
+ * are written first (content-addressed, never overwritten), then the
337
+ * plugin's label is set on `host` INSIDE the batch. Undo reverts the
338
+ * label; the plugin reloads the state the label points back to.
339
+ *
340
+ * `host` is a page item, a story (range), a page (`page:<id>`,
341
+ * `page:#n` — protocol 70 `setPageMetadata`) or the document (`doc` —
342
+ * `setDocumentMetadata`).
343
+ *
344
+ * Core holds ONE label per plugin per host, under exactly
345
+ * `x-paged:<pluginId>`, whose value is the envelope
346
+ * `{"v": <int >= 1>, "data": {…}}` (≤ 64 KiB). So `labelKey` is either
347
+ * - `x-paged:<pluginId>` — `labelValue` IS that whole envelope
348
+ * (JSON text), and replaces the label; or
349
+ * - `x-paged:<pluginId>.<sub>` — a SUB-KEY: the registry reads the
350
+ * current envelope, sets `data[<sub>]` to `labelValue` (parsed
351
+ * when it is JSON text, else kept as the string) and writes the
352
+ * merged envelope back. Several sub-keys on one host — in one
353
+ * write or across writes of one batch — merge into one label.
354
+ * Any other key is refused before anything applies.
355
+ * · `both` — the two together.
356
+ * · `rejected` — a typed no.
357
+ *
358
+ * `created` — the address of each object the write's `create` ops make,
359
+ * ONE per `create` op the planner received, in the order received (the
360
+ * registry splices them into `ObjectWriteResult.created` at the ops'
361
+ * batch positions). When the engine mints the id, write the placeholder
362
+ * `$h:<handle>` (the `bindCreated` handle of that insert) or `$created`
363
+ * (the batch's `createdId`) inside the address; the registry replaces it
364
+ * with the minted element's id after the commit. Omitted: each of those
365
+ * creates answers `""`. A list of the wrong length is refused (`failed`)
366
+ * before anything applies.
367
+ */
368
+ export type ObjectWrite = {
369
+ kind: "mutations";
370
+ mutations: readonly MutationInput[];
371
+ created?: readonly Address[];
372
+ } | {
373
+ kind: "state";
374
+ parts: readonly PartChange[];
375
+ host: Address;
376
+ labelKey: string;
377
+ labelValue: string;
378
+ created?: readonly Address[];
379
+ } | {
380
+ kind: "both";
381
+ mutations: readonly MutationInput[];
382
+ parts: readonly PartChange[];
383
+ host: Address;
384
+ labelKey: string;
385
+ labelValue: string;
386
+ created?: readonly Address[];
387
+ } | {
388
+ kind: "rejected";
389
+ reason: string;
390
+ };
391
+ /** What a kind's `list` receives. The registry ALSO post-filters the
392
+ * selector's predicates by reading paths through `get`, so honouring
393
+ * `predicates` in `list` is an optimisation, never a duty. */
394
+ export interface ObjectQuery {
395
+ /** The selector step that selected this kind (`plugin:x/layer[name="a"]`). */
396
+ selector?: Selector;
397
+ kind?: string;
398
+ /** The container the selector scoped the step to (`scope > kind`):
399
+ * a page item or story (a host), or one of this plugin's own
400
+ * addresses (`plugin:x/image/1 > plugin:x/layer`). Container scopes
401
+ * (`doc`, `page:`, `spread:`, `master:`, `layer:`) never reach `list`:
402
+ * the registry lists unscoped and keeps the objects whose `hostOf` lies
403
+ * in the container. */
404
+ within?: Address;
405
+ limit?: number;
406
+ /** The step's parsed predicates (this SDK's addition to ADR 323). */
407
+ predicates?: readonly ObjectPredicate[];
408
+ }
409
+ export interface ObjectPredicate {
410
+ key: string;
411
+ op: "=" | "!=" | "^=" | "*=";
412
+ value: string;
413
+ }
414
+ /** One object KIND a plugin contributes (ADR 323). Headless; live for the
415
+ * whole session; declared under `contributes.objectModel.kinds[]`. */
416
+ export interface ObjectKindContribution {
417
+ /** Plugin-local kind name (`layer`, `cell`). Addresses are
418
+ * `plugin:<manifest.id>/<kind>/<id>`. */
419
+ kind: string;
420
+ title: string;
421
+ /** At least one row; validated at registration. */
422
+ schema: readonly PropertySchema[];
423
+ content?: ContentSchema;
424
+ /** The core element hosting an object (frame / scene layer / page /
425
+ * `doc`): where its labels live (ADR 559), and how the registry scopes
426
+ * `page:x > plugin:…` selectors. May answer asynchronously. */
427
+ hostOf(address: Address): Address | null | Promise<Address | null>;
428
+ list(query: ObjectQuery): readonly Address[] | Promise<readonly Address[]>;
429
+ get(address: Address, path: PropertyPathName): ObjectValue | Promise<ObjectValue>;
430
+ set?(address: Address, path: PropertyPathName, value: unknown): ObjectWrite | Promise<ObjectWrite>;
431
+ /** Preferred over repeated `set`: many ops, one write, one undo step.
432
+ * Receives this kind's `set` / `create` / `delete` ops. Ignored when
433
+ * the contribution has a plugin-level `batch`. */
434
+ batch?(ops: readonly ObjectOp[]): ObjectWrite | Promise<ObjectWrite>;
435
+ }
436
+ /**
437
+ * A TYPED command (ADR 323): its args and result carry value types, so the
438
+ * CLI, Boa and a generated form can call it without knowing the plugin.
439
+ * `host.objects.invoke` validates `args` first. The untyped
440
+ * `contribute.command` stays for the migration (a shrink-only list).
441
+ */
442
+ export interface TypedCommandContribution<A = unknown, R = unknown> {
443
+ id: string;
444
+ title: string;
445
+ /** Usually `{ kind: "struct", fields }`. */
446
+ args: ValueType;
447
+ result?: ValueType;
448
+ handler(ctx: {
449
+ objects: ObjectsSurface;
450
+ }, args: A): R | Promise<R>;
451
+ }
452
+ export interface ObjectModelContribution {
453
+ kinds: readonly ObjectKindContribution[];
454
+ commands?: readonly TypedCommandContribution[];
455
+ /**
456
+ * PLUGIN-level planner: receives every `set` / `create` / `delete` op of
457
+ * a batch that targets ANY of this plugin's kinds, in batch order, and
458
+ * answers ONE write. Use it whenever two kinds share state (an image's
459
+ * layers and adjustments live in one stack): planned per kind, each
460
+ * kind would start from the same state and the second write would drop
461
+ * the first one's changes. When present, the kinds' own `batch`/`set`
462
+ * are not used for writes. Without it, ops are planned per kind, and
463
+ * two per-kind `state` writes landing on the same host are refused
464
+ * (`failed`, "conflicting state writes") rather than silently merged.
465
+ */
466
+ batch?(ops: readonly ObjectOp[]): ObjectWrite | Promise<ObjectWrite>;
467
+ }
468
+ /** What `contribute.objectModel` returns: a `Disposable` (ADR 323) that
469
+ * can also announce changes the engine does not see (wasm-held state). */
470
+ export interface ObjectModelHandle extends Disposable {
471
+ /** No argument = every object of this plugin may have changed. */
472
+ changed(addresses?: readonly Address[]): void;
473
+ }
474
+ export interface ObjectWriteResult {
475
+ applied: boolean;
476
+ reason?: string;
477
+ /** Engine undo steps the write took: a batch is ONE. */
478
+ undoSteps: 0 | 1;
479
+ /** This SDK's additions: the refusal code and the failing op. */
480
+ code?: ObjectErrorCode;
481
+ index?: number;
482
+ /** The engine outcome (minted ids for `create`/`$h:`). */
483
+ outcome?: MutationOutcome;
484
+ /** The address of every object a `create` op made, in op order (this
485
+ * SDK's addition; a core create needs engine protocol 71). */
486
+ created?: readonly Address[];
487
+ /** One entry per typed CORE command (`invoke` of `core.<op>`) the batch
488
+ * ran, in op order: its op index and the addresses of what it created
489
+ * (this SDK's addition, DESIGN.md §21.12). */
490
+ commands?: readonly ObjectCommandResult[];
491
+ }
492
+ /** What one typed core command of a batch made. */
493
+ export interface ObjectCommandResult {
494
+ index: number;
495
+ command: string;
496
+ created: readonly Address[];
497
+ }
498
+ export interface ObjectChange {
499
+ /** Empty = "anything may have changed" (core reports page-level damage
500
+ * today, not addresses). */
501
+ addresses: readonly Address[];
502
+ paths?: readonly string[];
503
+ origin: "core" | string;
504
+ }
505
+ export interface ObjectKindInfo {
506
+ owner: "core" | string;
507
+ /** Qualified: core kinds bare (`frame`), plugin kinds
508
+ * `plugin:<pluginId>/<kind>`. */
509
+ kind: string;
510
+ title: string;
511
+ /** The address form, e.g. `plugin:acme.notes/note/<id>`. */
512
+ addressForm: string;
513
+ }
514
+ export interface ObjectCommandInfo {
515
+ id: string;
516
+ title: string;
517
+ owner: string;
518
+ args: ValueType;
519
+ result?: ValueType;
520
+ }
521
+ /** `host.objects` — the one registry over core and every plugin. */
522
+ export interface ObjectsSurface {
523
+ query(selector: Selector, opts?: {
524
+ limit?: number;
525
+ }): Promise<Address[]>;
526
+ get(address: Address, path: PropertyPathName): Promise<ObjectValue>;
527
+ /** Every schema path of the object, read — except `heavy` rows (and
528
+ * `content`), which are read only with `{ content: true }`. */
529
+ getAll(address: Address, opts?: {
530
+ content?: boolean;
531
+ }): Promise<Record<string, ObjectValue>>;
532
+ set(address: Address, path: PropertyPathName, value: unknown, opts?: {
533
+ label?: string;
534
+ }): Promise<ObjectWriteResult>;
535
+ /** All-or-nothing: validated and planned before anything applies; core
536
+ * ops and plugin plans land as ONE engine batch = one undo step. */
537
+ batch(ops: readonly ObjectOp[], opts?: {
538
+ label?: string;
539
+ }): Promise<ObjectWriteResult>;
540
+ /** The rows of an address's kind, or of a qualified kind name. Empty
541
+ * when the kind is unknown. */
542
+ schema(addressOrKind: Address | string): Promise<readonly PropertySchema[]>;
543
+ kinds(): Promise<readonly ObjectKindInfo[]>;
544
+ /** Typed commands contributed through `contribute.objectModel`. */
545
+ commands(): Promise<readonly ObjectCommandInfo[]>;
546
+ /** Run a typed command. Rejects with an `Error` whose message starts
547
+ * with the refusal code (`unknownCommand: …`, `invalidValue: …`).
548
+ * A CORE command (`core.<op>`, listed by `commands()` with owner
549
+ * `"core"`) runs as ONE batch and answers `{ created, undoSteps }`;
550
+ * it also joins `batch` as an `invoke` op (one undo step with the
551
+ * batch's sets / creates / deletes). */
552
+ invoke(command: string, args: unknown): Promise<unknown>;
553
+ onDidChange(listener: (e: ObjectChange) => void, filter?: {
554
+ selector?: Selector;
555
+ }): Disposable;
556
+ }