@timeax/digital-service-engine 0.0.1

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.
@@ -0,0 +1,1572 @@
1
+ import { NodeProps } from 'reactflow';
2
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import React, { ReactNode } from 'react';
4
+
5
+ type PricingRole = "base" | "utility";
6
+ type FieldType = "custom" | (string & {});
7
+ /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
8
+ type QuantityMark = {
9
+ quantity?: {
10
+ valueBy: "value" | "length" | "eval";
11
+ code?: string;
12
+ multiply?: number;
13
+ clamp?: {
14
+ min?: number;
15
+ max?: number;
16
+ };
17
+ fallback?: number;
18
+ };
19
+ };
20
+ type UtilityMark = {
21
+ utility?: {
22
+ rate: number;
23
+ mode: "flat" | "per_quantity" | "per_value" | "percent";
24
+ valueBy?: "value" | "length";
25
+ percentBase?: "service_total" | "base_service" | "all";
26
+ label?: string;
27
+ };
28
+ };
29
+ type WithQuantityDefault = {
30
+ quantityDefault?: number;
31
+ };
32
+ /** ---------------- Core schema (as you designed) ---------------- */
33
+ interface BaseFieldUI {
34
+ name?: string;
35
+ label: string;
36
+ required?: boolean;
37
+ /** Host-defined prop names → typed UI nodes */
38
+ ui?: Record<string, Ui>;
39
+ /** Host-defined prop names → runtime default values (untyped base) */
40
+ defaults?: Record<string, unknown>;
41
+ }
42
+ type Ui = UiString | UiNumber | UiBoolean | UiAnyOf | UiArray | UiObject;
43
+ /** string */
44
+ interface UiString {
45
+ type: "string";
46
+ enum?: string[];
47
+ minLength?: number;
48
+ maxLength?: number;
49
+ pattern?: string;
50
+ format?: string;
51
+ }
52
+ /** number */
53
+ interface UiNumber {
54
+ type: "number";
55
+ minimum?: number;
56
+ maximum?: number;
57
+ multipleOf?: number;
58
+ }
59
+ /** boolean */
60
+ interface UiBoolean {
61
+ type: "boolean";
62
+ }
63
+ /** enumerated choices */
64
+ interface UiAnyOf {
65
+ type: "anyOf";
66
+ multiple?: boolean;
67
+ items: Array<{
68
+ type: "string" | "number" | "boolean";
69
+ title?: string;
70
+ description?: string;
71
+ value: string | number | boolean;
72
+ }>;
73
+ }
74
+ /** arrays: homogeneous (item) or tuple (items) */
75
+ interface UiArray {
76
+ type: "array";
77
+ item?: Ui;
78
+ items?: Ui[];
79
+ minItems?: number;
80
+ maxItems?: number;
81
+ uniqueItems?: boolean;
82
+ }
83
+ /** objects: nested props */
84
+ interface UiObject {
85
+ type: "object";
86
+ fields: Record<string, Ui>;
87
+ required?: string[];
88
+ order?: string[];
89
+ }
90
+ /** ---------------- Typed defaults helpers ---------------- */
91
+ /**
92
+ * UiValue<U>: given a Ui node U, infer the runtime value type.
93
+ */
94
+ type UiValue<U extends Ui> = U extends {
95
+ type: "string";
96
+ } ? string : U extends {
97
+ type: "number";
98
+ } ? number : U extends {
99
+ type: "boolean";
100
+ } ? boolean : U extends {
101
+ type: "anyOf";
102
+ multiple: true;
103
+ } ? Array<U["items"][number]["value"]> : U extends {
104
+ type: "anyOf";
105
+ } ? U["items"][number]["value"] : U extends {
106
+ type: "array";
107
+ item: infer I extends Ui;
108
+ } ? Array<UiValue<I>> : U extends {
109
+ type: "array";
110
+ items: infer T extends Ui[];
111
+ } ? {
112
+ [K in keyof T]: UiValue<T[K]>;
113
+ } : U extends {
114
+ type: "object";
115
+ fields: infer F extends Record<string, Ui>;
116
+ } ? {
117
+ [K in keyof F]?: UiValue<F[K]>;
118
+ } : unknown;
119
+ /**
120
+ * FieldWithTypedDefaults<T>: same shape as BaseFieldUI, but:
121
+ * - ui is a concrete map T (propName → Ui node)
122
+ * - defaults are auto-typed from T via UiValue
123
+ */
124
+ type FieldWithTypedDefaults<T extends Record<string, Ui>> = Omit<BaseFieldUI, "ui" | "defaults"> & {
125
+ ui: T;
126
+ defaults?: Partial<{
127
+ [K in keyof T]: UiValue<T[K]>;
128
+ }>;
129
+ };
130
+ type FieldOption = {
131
+ id: string;
132
+ label: string;
133
+ value?: string | number;
134
+ service_id?: number;
135
+ pricing_role?: PricingRole;
136
+ meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
137
+ };
138
+ type Field = BaseFieldUI & {
139
+ id: string;
140
+ type: FieldType;
141
+ bind_id?: string | string[];
142
+ name?: string;
143
+ options?: FieldOption[];
144
+ description?: string;
145
+ component?: string;
146
+ pricing_role?: PricingRole;
147
+ meta?: Record<string, unknown> & QuantityMark & UtilityMark & {
148
+ multi?: boolean;
149
+ };
150
+ } & ({
151
+ button?: false;
152
+ service_id?: undefined;
153
+ } | ({
154
+ button: true;
155
+ service_id?: number;
156
+ } & WithQuantityDefault));
157
+ type ConstraintKey = string;
158
+ /**
159
+ * Back-compat alias: older code may still import FlagKey.
160
+ * Keeping this prevents a wave of TS errors while still allowing any string key.
161
+ */
162
+ type FlagKey = ConstraintKey;
163
+ type Tag = {
164
+ id: string;
165
+ label: string;
166
+ bind_id?: string;
167
+ service_id?: number;
168
+ includes?: string[];
169
+ excludes?: string[];
170
+ meta?: Record<string, unknown> & WithQuantityDefault;
171
+ /**
172
+ * Which flags are set for this tag. If a flag is not set, it's inherited from the nearest ancestor with a value set.
173
+ */
174
+ constraints?: Partial<Record<ConstraintKey, boolean>>;
175
+ /** Which ancestor defined the *effective* value for each flag (nearest source). */
176
+ constraints_origin?: Partial<Record<ConstraintKey, string>>;
177
+ /**
178
+ * Present only when a child explicitly set a different value but was overridden
179
+ * by an ancestor during normalisation.
180
+ */
181
+ constraints_overrides?: Partial<Record<ConstraintKey, {
182
+ from: boolean;
183
+ to: boolean;
184
+ origin: string;
185
+ }>>;
186
+ };
187
+ type ServiceProps = {
188
+ order_for_tags?: Record<string, string[]>;
189
+ filters: Tag[];
190
+ fields: Field[];
191
+ includes_for_buttons?: Record<string, string[]>;
192
+ excludes_for_buttons?: Record<string, string[]>;
193
+ schema_version?: string;
194
+ fallbacks?: ServiceFallback;
195
+ };
196
+ type ServiceIdRef = number | string;
197
+ type NodeIdRef = string;
198
+ type ServiceFallback = {
199
+ /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
200
+ nodes?: Record<NodeIdRef, ServiceIdRef[]>;
201
+ /** Primary→fallback list used when no node-scoped entry is present */
202
+ global?: Record<ServiceIdRef, ServiceIdRef[]>;
203
+ };
204
+
205
+ type NodeKind = "tag" | "field" | "comment" | "option";
206
+ type EdgeKind = "child" | "bind" | "include" | "exclude" | "error" | "anchor";
207
+ type GraphNode = {
208
+ id: string;
209
+ kind: NodeKind;
210
+ bind_type?: "bound" | "utility" | null;
211
+ errors?: string[];
212
+ label: string;
213
+ };
214
+ type GraphEdge = {
215
+ from: string;
216
+ to: string;
217
+ kind: EdgeKind;
218
+ meta?: Record<string, unknown>;
219
+ };
220
+ type GraphSnapshot = {
221
+ nodes: GraphNode[];
222
+ edges: GraphEdge[];
223
+ };
224
+ type FlowNode = NodeProps<{
225
+ node: GraphNode;
226
+ [x: string]: any;
227
+ }>;
228
+
229
+ type CommentId = string;
230
+ type ThreadId = string;
231
+ type CommentAnchor = {
232
+ type: "node";
233
+ nodeId: string;
234
+ offset?: {
235
+ dx: number;
236
+ dy: number;
237
+ };
238
+ } | {
239
+ type: "edge";
240
+ edgeId: string;
241
+ t?: number;
242
+ } | {
243
+ type: "free";
244
+ position: {
245
+ x: number;
246
+ y: number;
247
+ };
248
+ };
249
+ type CommentMessage = {
250
+ id: CommentId;
251
+ isMain?: boolean;
252
+ authorId?: string;
253
+ authorName?: string;
254
+ body: string;
255
+ createdAt: number;
256
+ editedAt?: number;
257
+ meta?: Record<string, unknown>;
258
+ };
259
+ type CommentThread = {
260
+ id: ThreadId;
261
+ anchor?: CommentAnchor;
262
+ resolved: boolean;
263
+ createdAt: number;
264
+ updatedAt: number;
265
+ messages: CommentMessage[];
266
+ meta?: Record<string, unknown>;
267
+ _sync?: "pending" | "synced" | "error";
268
+ };
269
+
270
+ type Viewport = {
271
+ x: number;
272
+ y: number;
273
+ zoom: number;
274
+ };
275
+ type NodePos = {
276
+ x: number;
277
+ y: number;
278
+ };
279
+ type NodePositions = Record<string, NodePos>;
280
+ type DraftWire = {
281
+ from: string;
282
+ kind: EdgeKind;
283
+ };
284
+ type CanvasState = {
285
+ graph: GraphSnapshot;
286
+ positions: NodePositions;
287
+ selection: Set<string>;
288
+ highlighted: Set<string>;
289
+ hoverId?: string;
290
+ viewport: Viewport;
291
+ draftWire?: DraftWire;
292
+ version: number;
293
+ };
294
+ type CanvasEvents = {
295
+ "graph:update": GraphSnapshot;
296
+ "state:change": CanvasState;
297
+ "selection:change": {
298
+ ids: string[];
299
+ };
300
+ "viewport:change": Viewport;
301
+ "hover:change": {
302
+ id?: string;
303
+ };
304
+ "wire:preview": {
305
+ from: string;
306
+ to?: string;
307
+ kind: EdgeKind;
308
+ };
309
+ "wire:commit": {
310
+ from: string;
311
+ to: string;
312
+ kind: EdgeKind;
313
+ };
314
+ "wire:cancel": {
315
+ from: string;
316
+ };
317
+ error: {
318
+ message: string;
319
+ code?: string;
320
+ meta?: any;
321
+ };
322
+ "comment:thread:create": {
323
+ thread: CommentThread;
324
+ };
325
+ "comment:thread:update": {
326
+ thread: CommentThread;
327
+ };
328
+ "comment:thread:delete": {
329
+ threadId: string;
330
+ };
331
+ "comment:message:create": {
332
+ threadId: string;
333
+ message: CommentMessage;
334
+ };
335
+ "comment:resolve": {
336
+ thread: CommentThread;
337
+ resolved: boolean;
338
+ };
339
+ "comment:move": {
340
+ thread: CommentThread;
341
+ };
342
+ "comment:select": {
343
+ threadId?: string;
344
+ };
345
+ "edge:change": EdgeKind;
346
+ "comment:sync": {
347
+ op: "create_thread" | "add_message" | "edit_message" | "delete_message" | "move_thread" | "resolve_thread" | "delete_thread";
348
+ threadId: string;
349
+ messageId?: string;
350
+ status: "scheduled" | "retrying" | "succeeded" | "failed" | "cancelled";
351
+ attempt: number;
352
+ nextDelayMs?: number;
353
+ error?: any;
354
+ };
355
+ };
356
+ type NodeView = GraphNode & {
357
+ position?: NodePos;
358
+ };
359
+ type EdgeView = GraphEdge;
360
+ type CanvasOptions = {
361
+ initialViewport?: Partial<Viewport>;
362
+ autoEmitState?: boolean;
363
+ };
364
+
365
+ type CommentNode = {
366
+ id: string;
367
+ text: string;
368
+ status: "open" | "resolved";
369
+ anchor?: {
370
+ kind: "tag" | "field" | "option";
371
+ id: string;
372
+ };
373
+ replies?: Array<{
374
+ id: string;
375
+ text: string;
376
+ created_at: string;
377
+ author?: string;
378
+ }>;
379
+ xy?: {
380
+ x: number;
381
+ y: number;
382
+ };
383
+ meta?: Record<string, unknown>;
384
+ };
385
+ type EdgeRoute = {
386
+ id: string;
387
+ points: Array<{
388
+ x: number;
389
+ y: number;
390
+ }>;
391
+ };
392
+ type LayoutState = {
393
+ canvas: CanvasState;
394
+ edges?: EdgeRoute[];
395
+ };
396
+ type EditorSnapshot = {
397
+ props: ServiceProps;
398
+ layout?: LayoutState;
399
+ comments?: CommentNode[];
400
+ meta?: Record<string, unknown>;
401
+ };
402
+
403
+ type TimeRangeEstimate = {
404
+ min_seconds?: number;
405
+ max_seconds?: number;
406
+ label?: string;
407
+ meta?: Record<string, unknown>;
408
+ };
409
+ type SpeedEstimate = {
410
+ amount?: number;
411
+ per?: "minute" | "hour" | "day" | "week" | "month";
412
+ unit?: string;
413
+ label?: string;
414
+ meta?: Record<string, unknown>;
415
+ };
416
+ type ServiceEstimates = {
417
+ start?: TimeRangeEstimate;
418
+ speed?: SpeedEstimate;
419
+ average?: TimeRangeEstimate;
420
+ meta?: Record<string, unknown>;
421
+ };
422
+ type ServiceFlag = {
423
+ enabled: boolean;
424
+ description: string;
425
+ meta?: Record<string, unknown>;
426
+ };
427
+ type IdType = string | number;
428
+ type ServiceFlags = Record<string, ServiceFlag>;
429
+ type DgpServiceCapability = {
430
+ id: IdType;
431
+ name?: string;
432
+ rate: number;
433
+ min?: number;
434
+ max?: number;
435
+ category?: string;
436
+ flags?: ServiceFlags;
437
+ estimates?: ServiceEstimates;
438
+ meta?: Record<string, unknown>;
439
+ [x: string]: any;
440
+ };
441
+ type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
442
+
443
+ type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context";
444
+ type ValidationError = {
445
+ code: ValidationCode;
446
+ message: string;
447
+ severity: "error" | "warning" | "info";
448
+ nodeId?: string;
449
+ details?: Record<string, unknown> & {
450
+ affectedIds?: string[];
451
+ };
452
+ };
453
+ type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
454
+ /**
455
+ * Host-extensible service filter clause.
456
+ * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
457
+ */
458
+ type ServiceWhereClause = {
459
+ path: string;
460
+ op?: ServiceWhereOp;
461
+ value?: unknown;
462
+ };
463
+ type DynamicRule = {
464
+ id: string;
465
+ label?: string;
466
+ scope: "global" | "visible_group";
467
+ subject: "services";
468
+ /**
469
+ * Package-level filter surface:
470
+ * - role/tag/field are core to the builder schema
471
+ * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
472
+ */
473
+ filter?: {
474
+ role?: "base" | "utility" | "both";
475
+ tag_id?: string | string[];
476
+ field_id?: string | string[];
477
+ where?: ServiceWhereClause[];
478
+ };
479
+ /**
480
+ * Projection is intentionally open:
481
+ * hosts may project custom service properties.
482
+ */
483
+ projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
484
+ op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
485
+ value?: number | boolean;
486
+ severity?: "error" | "warning";
487
+ message?: string;
488
+ };
489
+ type ValidatorOptions = {
490
+ serviceMap?: DgpServiceMap;
491
+ allowUnsafe?: boolean;
492
+ selectedOptionKeys?: string[];
493
+ globalUtilityGuard?: boolean;
494
+ policies?: DynamicRule[];
495
+ fallbackSettings?: FallbackSettings;
496
+ };
497
+ type RatePolicy = {
498
+ kind: "lte_primary";
499
+ } | {
500
+ kind: "within_pct";
501
+ pct: number;
502
+ } | {
503
+ kind: "at_least_pct_lower";
504
+ pct: number;
505
+ };
506
+ type FallbackSettings = {
507
+ /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
508
+ requireConstraintFit?: boolean;
509
+ /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
510
+ ratePolicy?: RatePolicy;
511
+ /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
512
+ selectionStrategy?: "priority" | "cheapest";
513
+ /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
514
+ mode?: "strict" | "dev";
515
+ };
516
+
517
+ /** Options you can set on the builder (used for validation/visibility) */
518
+ type BuilderOptions = Omit<ValidatorOptions, "serviceMap"> & {
519
+ serviceMap?: DgpServiceMap;
520
+ /** max history entries for undo/redo */
521
+ historyLimit?: number;
522
+ /**
523
+ * Field ids whose options should be shown as nodes in the graph.
524
+ * If a field id is NOT in this set, its options are not materialized as nodes:
525
+ * - include/exclude wires keyed by an option id will be drawn from the FIELD instead.
526
+ */
527
+ showOptionNodes?: Set<string> | string[];
528
+ };
529
+ interface Builder {
530
+ /** Replace current payload (injects root if missing, rebuilds indexes) */
531
+ load(props: ServiceProps): void;
532
+ /** Graph for visualisation */
533
+ tree(): GraphSnapshot;
534
+ /** Deterministic save payload (drops unbound utility fields, prunes dead maps) */
535
+ cleanedProps(): ServiceProps;
536
+ /** Validation errors for current state */
537
+ errors(): ValidationError[];
538
+ /**
539
+ * Compute IDs of fields visible under a tag.
540
+ * If selectedOptionKeys provided, applies option-level include/exclude.
541
+ * NOTE: keys are “button ids”: either option.id or field.id for option-less buttons.
542
+ */
543
+ visibleFields(tagId: string, selectedOptionKeys?: string[]): string[];
544
+ /** Update builder options (validator context etc.) */
545
+ setOptions(patch: Partial<BuilderOptions>): void;
546
+ /** History */
547
+ undo(): boolean;
548
+ redo(): boolean;
549
+ /** Access the current props (already normalised) */
550
+ getProps(): ServiceProps;
551
+ /** Service map for validation/rules */
552
+ getServiceMap(): DgpServiceMap;
553
+ getConstraints(): {
554
+ id: string;
555
+ label: string;
556
+ description: string;
557
+ value: string;
558
+ }[];
559
+ }
560
+
561
+ type Env = "client" | "workspace";
562
+ type VisibleGroup = {
563
+ tagId?: string;
564
+ tag?: Tag;
565
+ fields: Field[];
566
+ fieldIds: string[];
567
+ parentTags?: Tag[];
568
+ childrenTags?: Tag[];
569
+ /** In order of selection: tag base (unless overridden) then selected options */
570
+ services?: DgpServiceCapability[];
571
+ };
572
+ type VisibleGroupResult = {
573
+ kind: "single";
574
+ group: VisibleGroup;
575
+ } | {
576
+ kind: "multi";
577
+ groups: string[];
578
+ };
579
+ type ChangeEvt = {
580
+ ids: string[];
581
+ primary?: string;
582
+ };
583
+ type Listener = (e: ChangeEvt) => void;
584
+ type SelectionOptions = {
585
+ env?: Env;
586
+ rootTagId?: string;
587
+ /** Resolve service capability from an id (used for `services` array) */
588
+ resolveService?: (id: any) => DgpServiceCapability | undefined;
589
+ };
590
+ declare class Selection {
591
+ private readonly builder;
592
+ private readonly opts;
593
+ private set;
594
+ private primaryId;
595
+ private currentTagId;
596
+ private onChangeFns;
597
+ constructor(builder: Builder, opts: SelectionOptions);
598
+ replace(id?: string | null): void;
599
+ add(id: string): void;
600
+ remove(id: string): void;
601
+ toggle(id: string): void;
602
+ many(ids: Iterable<string>, primary?: string): void;
603
+ clear(): void;
604
+ all(): ReadonlySet<string>;
605
+ has(id: string): boolean;
606
+ primary(): string | undefined;
607
+ currentTag(): string | undefined;
608
+ onChange(fn: Listener): () => void;
609
+ visibleGroup(): VisibleGroupResult;
610
+ private emit;
611
+ private updateCurrentTagFrom;
612
+ private resolveTagContextId;
613
+ private selectedButtonTriggerIds;
614
+ private computeGroupForTag;
615
+ private addServiceByRole;
616
+ private findOptionById;
617
+ }
618
+
619
+ type EditorEvents = {
620
+ "editor:command": {
621
+ name: string;
622
+ payload?: any;
623
+ };
624
+ "editor:change": {
625
+ props: ServiceProps;
626
+ reason: string;
627
+ command?: string;
628
+ };
629
+ "editor:undo": {
630
+ stackSize: number;
631
+ index: number;
632
+ };
633
+ "editor:redo": {
634
+ stackSize: number;
635
+ index: number;
636
+ };
637
+ "editor:error": {
638
+ message: string;
639
+ code?: string;
640
+ meta?: any;
641
+ };
642
+ };
643
+ type Command = {
644
+ name: string;
645
+ do(): void;
646
+ undo(): void;
647
+ };
648
+ type EditorOptions = {
649
+ historyLimit?: number;
650
+ validateAfterEach?: boolean;
651
+ /** Sync existence check; return true if the service exists. */
652
+ serviceExists?: (id: number) => boolean;
653
+ /** Optional local index; used if serviceExists is not provided. */
654
+ serviceMap?: Record<number, unknown>;
655
+ /** Raw policies JSON; will be compiled on demand by filterServicesForVisibleGroup. */
656
+ policiesRaw?: unknown;
657
+ selectionProps?: SelectionOptions;
658
+ };
659
+ type ConnectKind = "bind" | "include" | "exclude";
660
+
661
+ interface ButtonValue {
662
+ id: string;
663
+ value: string | number;
664
+ service_id?: number;
665
+ pricing_role?: "base" | "utility";
666
+ meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
667
+ }
668
+ type Scalar = string | number | boolean | ButtonValue | null;
669
+ type UtilityMode = "flat" | "per_quantity" | "per_value" | "percent";
670
+ type QuantityRule$1 = {
671
+ valueBy: "value" | "length" | "eval";
672
+ code?: string;
673
+ };
674
+ type UtilityLineItem = {
675
+ nodeId: string;
676
+ mode: UtilityMode;
677
+ rate: number;
678
+ inputs: {
679
+ quantity: number;
680
+ value?: Scalar | Scalar[];
681
+ valueBy?: "value" | "length" | "eval";
682
+ evalCodeUsed?: boolean;
683
+ };
684
+ };
685
+ type ServiceFallbacks = {
686
+ nodes?: Record<string, Array<string | number>>;
687
+ global?: Record<string | number, Array<string | number>>;
688
+ };
689
+ type FallbackDiagnostics = {
690
+ scope: "node" | "global";
691
+ nodeId?: string;
692
+ primary: string | number;
693
+ candidate: string | number;
694
+ reasons: Array<"rate_violation" | "constraint_mismatch" | "unknown_service" | "ambiguous_context">;
695
+ };
696
+ type SnapshotContext = {
697
+ /** The single active tag id for this order */
698
+ tag: string;
699
+ /** Effective (post-propagation) constraints on that tag */
700
+ constraints: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
701
+ /**
702
+ * Per-node evaluation context:
703
+ * - For the active tag node itself: the same tag id.
704
+ * - For an option node: parent's field.bind_id must include this tag to be applicable; otherwise null.
705
+ * - For a field node (optional to include later): same rule as option, derived from field.bind_id.
706
+ */
707
+ nodeContexts: Record<string, string | null>;
708
+ /** Client pruning policy used (so server can mirror/compare). */
709
+ policy: {
710
+ ratePolicy: {
711
+ kind: "lte_primary" | "none";
712
+ thresholdPct?: number;
713
+ };
714
+ requireConstraintFit: boolean;
715
+ };
716
+ };
717
+ type OrderSnapshot = {
718
+ version: "1";
719
+ mode: "prod" | "dev";
720
+ builtAt: string;
721
+ selection: {
722
+ tag: string;
723
+ fields: Array<{
724
+ id: string;
725
+ type: string;
726
+ selectedOptions?: string[];
727
+ }>;
728
+ };
729
+ inputs: {
730
+ form: Record<string, Scalar | Scalar[]>;
731
+ selections: Record<string, string[]>;
732
+ };
733
+ quantity: number;
734
+ quantitySource: {
735
+ kind: "field" | "tag" | "option" | "default";
736
+ id?: string;
737
+ rule?: QuantityRule$1;
738
+ defaultedFromHost?: boolean;
739
+ };
740
+ services: Array<string | number>;
741
+ serviceMap: Record<string, Array<string | number>>;
742
+ fallbacks?: {
743
+ nodes?: Record<string, Array<string | number>>;
744
+ global?: Record<string | number, Array<string | number>>;
745
+ };
746
+ utilities?: UtilityLineItem[];
747
+ warnings?: {
748
+ utility?: Array<{
749
+ nodeId: string;
750
+ reason: string;
751
+ }>;
752
+ fallbacks?: FallbackDiagnostics[];
753
+ };
754
+ meta?: {
755
+ schema_version?: string;
756
+ workspaceId?: string;
757
+ builder?: {
758
+ commit?: string;
759
+ };
760
+ context?: SnapshotContext;
761
+ };
762
+ };
763
+
764
+ /** Exported alias so the schema generator can target an array */
765
+ type AdminPolicies = DynamicRule[];
766
+
767
+ type EventMap = Record<string, unknown>;
768
+ declare class EventBus<E extends EventMap> {
769
+ private listeners;
770
+ on<K extends keyof E>(event: K, handler: (payload: E[K]) => void): () => void;
771
+ once<K extends keyof E>(event: K, handler: (payload: E[K]) => void): () => void;
772
+ emit<K extends keyof E>(event: K, payload: E[K]): void;
773
+ clear(): void;
774
+ }
775
+
776
+ type PolicyDiagnostic = {
777
+ ruleIndex: number;
778
+ ruleId?: string;
779
+ severity: "error" | "warning";
780
+ message: string;
781
+ path?: string;
782
+ };
783
+
784
+ interface BackendError {
785
+ readonly meta?: any;
786
+ readonly code: string;
787
+ readonly message: string;
788
+ readonly status?: number;
789
+ readonly hint?: string;
790
+ readonly cause?: unknown;
791
+ }
792
+ type BackendResult<T> = {
793
+ ok: true;
794
+ value: T;
795
+ } | {
796
+ ok: false;
797
+ error: BackendError;
798
+ };
799
+ type Result<T> = Promise<BackendResult<T>>;
800
+ interface Actor {
801
+ readonly id: string;
802
+ readonly name?: string;
803
+ readonly roles?: readonly string[];
804
+ readonly meta?: Readonly<Record<string, unknown>>;
805
+ }
806
+ interface Author {
807
+ readonly id: string;
808
+ readonly name: string;
809
+ readonly handle?: string;
810
+ readonly avatarUrl?: string;
811
+ readonly meta?: Readonly<Record<string, unknown>>;
812
+ readonly createdAt?: string;
813
+ readonly updatedAt?: string;
814
+ }
815
+ type PermissionsMap = Readonly<Record<string, boolean>>;
816
+ interface Branch {
817
+ readonly id: string;
818
+ readonly name: string;
819
+ readonly isMain: boolean;
820
+ readonly headVersionId?: string;
821
+ readonly createdAt: string;
822
+ readonly updatedAt: string;
823
+ }
824
+ interface MergeResult {
825
+ readonly sourceId: string;
826
+ readonly targetId: string;
827
+ readonly conflicts?: number;
828
+ readonly message?: string;
829
+ }
830
+ type ServicesInput = readonly DgpServiceCapability[] | DgpServiceMap;
831
+ interface ServicesBackend {
832
+ get(workspaceId: string): Result<ServicesInput>;
833
+ refresh(workspaceId: string, params?: Readonly<{
834
+ since?: number | string;
835
+ }>): Result<ServicesInput>;
836
+ }
837
+ interface BranchParticipant {
838
+ readonly id: string;
839
+ readonly workspaceId: string;
840
+ readonly branchId: string;
841
+ /** points into Authors directory */
842
+ readonly authorId: string;
843
+ /** roles are intentionally loose; host decides taxonomy */
844
+ readonly roles?: readonly string[];
845
+ readonly canRead: boolean;
846
+ readonly canWrite: boolean;
847
+ readonly meta?: Readonly<Record<string, unknown>>;
848
+ readonly createdAt?: string;
849
+ readonly updatedAt?: string;
850
+ }
851
+ interface BranchAccessBackend {
852
+ listParticipants(workspaceId: string, branchId: string): Result<readonly BranchParticipant[]>;
853
+ refreshParticipants(workspaceId: string, branchId: string, params?: Readonly<{
854
+ since?: number | string;
855
+ }>): Result<readonly BranchParticipant[]>;
856
+ }
857
+ interface ServiceSnapshot {
858
+ readonly schema_version: string;
859
+ readonly data: EditorSnapshot;
860
+ readonly meta?: Readonly<Record<string, unknown>>;
861
+ }
862
+ interface Draft {
863
+ readonly id: string;
864
+ readonly branchId: string;
865
+ readonly status: "uncommitted";
866
+ readonly etag?: string;
867
+ readonly meta?: Readonly<Record<string, unknown>>;
868
+ readonly createdAt: string;
869
+ readonly updatedAt: string;
870
+ }
871
+ interface Commit {
872
+ readonly id: string;
873
+ readonly branchId: string;
874
+ readonly message?: string;
875
+ readonly versionId?: string;
876
+ readonly etag?: string;
877
+ readonly createdAt: string;
878
+ }
879
+ interface SnapshotsLoadResult {
880
+ readonly head?: Commit;
881
+ readonly draft?: Draft;
882
+ readonly snapshot: ServiceSnapshot;
883
+ }
884
+ interface SnapshotsBackend {
885
+ load(params: Readonly<{
886
+ workspaceId: string;
887
+ branchId: string;
888
+ actorId: string;
889
+ versionId?: string;
890
+ }>): Result<SnapshotsLoadResult>;
891
+ autosave(params: Readonly<{
892
+ workspaceId: string;
893
+ branchId: string;
894
+ actorId: string;
895
+ snapshot: ServiceSnapshot;
896
+ clientId?: string;
897
+ since?: number | string;
898
+ etag?: string;
899
+ }>): Result<Readonly<{
900
+ draft: Draft;
901
+ }>>;
902
+ save(params: Readonly<{
903
+ workspaceId: string;
904
+ branchId: string;
905
+ actorId: string;
906
+ snapshot: ServiceSnapshot;
907
+ message?: string;
908
+ draftId?: string;
909
+ etag?: string;
910
+ }>): Result<Readonly<{
911
+ commit: Commit;
912
+ }>>;
913
+ publish(params: Readonly<{
914
+ workspaceId: string;
915
+ actorId: string;
916
+ draftId: string;
917
+ message?: string;
918
+ }>): Result<Readonly<{
919
+ commit: Commit;
920
+ }>>;
921
+ discard(params: Readonly<{
922
+ workspaceId: string;
923
+ actorId: string;
924
+ draftId: string;
925
+ }>): Result<void>;
926
+ refresh(params: Readonly<{
927
+ workspaceId: string;
928
+ branchId: string;
929
+ actorId: string;
930
+ since?: number | string;
931
+ }>): Result<Readonly<{
932
+ head?: Commit;
933
+ draft?: Draft;
934
+ }>>;
935
+ }
936
+ interface TemplateValidator {
937
+ readonly type: string;
938
+ readonly rule?: unknown;
939
+ readonly message?: string;
940
+ }
941
+ interface FieldTemplate {
942
+ readonly id: string;
943
+ /** Unique key (per workspace, optionally per branch) used to reference this template */
944
+ readonly key: string;
945
+ readonly name: string;
946
+ /** logical kind e.g. "text", "number", "date", "select", "relation", ... */
947
+ readonly kind: string;
948
+ /** Optional branch scoping (can be global if undefined) */
949
+ readonly branchId?: string;
950
+ /** Canonical, builder-consumable definition (shape up to your app) */
951
+ readonly definition: Readonly<Record<string, unknown>>;
952
+ /** Default values the editor may inject when using this template */
953
+ readonly defaults?: Readonly<Record<string, unknown>>;
954
+ /** UI metadata (icons, color, sizing, render hints, etc.) */
955
+ readonly ui?: Readonly<Record<string, unknown>>;
956
+ /** Client- or server-side validators */
957
+ readonly validators?: readonly TemplateValidator[];
958
+ readonly tags?: readonly string[];
959
+ readonly category?: string;
960
+ /** Published templates are selectable by default in the editor palette */
961
+ readonly published: boolean;
962
+ /** Incremented on every update */
963
+ readonly version: number;
964
+ readonly createdAt: string;
965
+ readonly updatedAt: string;
966
+ }
967
+ /** Narrow list/search input */
968
+ interface TemplatesListParams {
969
+ readonly workspaceId: string;
970
+ readonly branchId?: string;
971
+ readonly q?: string;
972
+ readonly tags?: readonly string[];
973
+ readonly category?: string;
974
+ readonly since?: string | number;
975
+ }
976
+ interface TemplateCreateInput {
977
+ readonly key?: string;
978
+ readonly name: string;
979
+ readonly kind: string;
980
+ readonly branchId?: string;
981
+ readonly definition: Readonly<Record<string, unknown>>;
982
+ readonly defaults?: Readonly<Record<string, unknown>>;
983
+ readonly ui?: Readonly<Record<string, unknown>>;
984
+ readonly validators?: readonly TemplateValidator[];
985
+ readonly tags?: readonly string[];
986
+ readonly category?: string;
987
+ readonly published?: boolean;
988
+ }
989
+ interface TemplateUpdatePatch {
990
+ readonly name?: string;
991
+ readonly kind?: string;
992
+ readonly branchId?: string | null;
993
+ readonly definition?: Readonly<Record<string, unknown>>;
994
+ readonly defaults?: Readonly<Record<string, unknown>> | null;
995
+ readonly ui?: Readonly<Record<string, unknown>> | null;
996
+ readonly validators?: readonly TemplateValidator[] | null;
997
+ readonly tags?: readonly string[] | null;
998
+ readonly category?: string | null;
999
+ readonly published?: boolean;
1000
+ }
1001
+ interface TemplatesBackend {
1002
+ list(params: TemplatesListParams): Result<readonly FieldTemplate[]>;
1003
+ get(id: string): Result<FieldTemplate | null>;
1004
+ getByKey(workspaceId: string, key: string, branchId?: string): Result<FieldTemplate | null>;
1005
+ create(workspaceId: string, input: TemplateCreateInput): Result<FieldTemplate>;
1006
+ update(id: string, patch: TemplateUpdatePatch): Result<FieldTemplate>;
1007
+ clone(source: Readonly<{
1008
+ id?: string;
1009
+ key?: string;
1010
+ }>, opts?: Readonly<{
1011
+ newKey?: string;
1012
+ name?: string;
1013
+ branchId?: string;
1014
+ asDraft?: boolean;
1015
+ }>): Result<FieldTemplate>;
1016
+ publish(id: string): Result<FieldTemplate>;
1017
+ unpublish(id: string): Result<FieldTemplate>;
1018
+ delete(id: string): Result<void>;
1019
+ refresh(params: Omit<TemplatesListParams, "q" | "tags" | "category">): Result<readonly FieldTemplate[]>;
1020
+ }
1021
+ interface AuthorsBackend {
1022
+ list(workspaceId: string): Result<readonly Author[]>;
1023
+ get(authorId: string): Result<Author | null>;
1024
+ refresh(workspaceId: string): Result<readonly Author[]>;
1025
+ }
1026
+ interface PermissionsBackend {
1027
+ get(workspaceId: string, actor: Actor): Result<PermissionsMap>;
1028
+ refresh(workspaceId: string, actor: Actor): Result<PermissionsMap>;
1029
+ }
1030
+ interface BranchesBackend {
1031
+ list(workspaceId: string): Result<readonly Branch[]>;
1032
+ create(workspaceId: string, name: string, opts?: Readonly<{
1033
+ fromId?: string;
1034
+ }>): Result<Branch>;
1035
+ setMain(workspaceId: string, branchId: string): Result<Branch>;
1036
+ merge(workspaceId: string, sourceId: string, targetId: string): Result<MergeResult>;
1037
+ delete(workspaceId: string, branchId: string): Result<void>;
1038
+ refresh(workspaceId: string): Result<readonly Branch[]>;
1039
+ }
1040
+ interface WorkspaceInfo {
1041
+ readonly id: string;
1042
+ readonly name: string;
1043
+ readonly description?: string;
1044
+ readonly createdAt: string;
1045
+ readonly updatedAt: string;
1046
+ readonly meta?: Readonly<Record<string, unknown>>;
1047
+ }
1048
+ /**
1049
+ * Shared request scope for host backends (workspace + optional branch + optional actor).
1050
+ * This is intentionally transport-agnostic and minimal so other layers (e.g. canvas/comments)
1051
+ * can reuse it without redefining identity types.
1052
+ */
1053
+ interface BackendScope {
1054
+ readonly workspaceId: string;
1055
+ readonly actorId: string;
1056
+ readonly branchId: string;
1057
+ }
1058
+ /**
1059
+ * Generic canvas comments backend contract.
1060
+ *
1061
+ * We keep this in workspace backend so HOSTS have a single place to implement
1062
+ * transport-agnostic interfaces.
1063
+ *
1064
+ * The canvas module can “bind” these generics to its concrete types.
1065
+ */
1066
+ interface CommentsBackend<ThreadDTO = unknown, MessageDTO = unknown, AnchorDTO = unknown> {
1067
+ listThreads(ctx: BackendScope): Result<readonly ThreadDTO[]>;
1068
+ createThread(ctx: BackendScope, input: {
1069
+ anchor?: AnchorDTO;
1070
+ body: string;
1071
+ meta?: Record<string, unknown>;
1072
+ }): Result<ThreadDTO>;
1073
+ addMessage(ctx: BackendScope, input: {
1074
+ threadId: string;
1075
+ body: string;
1076
+ meta?: Record<string, unknown>;
1077
+ }): Result<MessageDTO>;
1078
+ editMessage(ctx: BackendScope, input: {
1079
+ threadId: string;
1080
+ messageId: string;
1081
+ body: string;
1082
+ }): Result<MessageDTO>;
1083
+ deleteMessage(ctx: BackendScope, input: {
1084
+ threadId: string;
1085
+ messageId: string;
1086
+ }): Result<void>;
1087
+ moveThread(ctx: BackendScope, input: {
1088
+ threadId: string;
1089
+ anchor: AnchorDTO;
1090
+ }): Result<ThreadDTO>;
1091
+ resolveThread(ctx: BackendScope, input: {
1092
+ threadId: string;
1093
+ resolved: boolean;
1094
+ }): Result<ThreadDTO>;
1095
+ deleteThread(ctx: BackendScope, input: {
1096
+ threadId: string;
1097
+ }): Result<void>;
1098
+ }
1099
+ /**
1100
+ * Policies are stored as raw JSON for round-tripping (comments/formatting, etc.),
1101
+ * and compiled into DynamicRule[] for runtime validation.
1102
+ *
1103
+ * Scope rules:
1104
+ * - If branchId is provided -> branch-scoped policies
1105
+ * - If branchId is omitted -> workspace-scoped policies
1106
+ */
1107
+ interface PolicyScope extends BackendScope {
1108
+ }
1109
+ interface PoliciesLoadResult {
1110
+ /** the raw JSON the host stored (authoring format) */
1111
+ readonly raw: unknown;
1112
+ /** optional precompiled rules (host may compile server-side) */
1113
+ readonly compiled?: readonly DynamicRule[];
1114
+ /** optional diagnostics (host may compile server-side) */
1115
+ readonly diagnostics?: readonly PolicyDiagnostic[];
1116
+ readonly updatedAt?: string;
1117
+ readonly etag?: string;
1118
+ }
1119
+ /**
1120
+ * Transport contract for policy storage/compilation.
1121
+ *
1122
+ * - UI can compile locally via compilePolicies()
1123
+ * - Host can also compile/validate server-side by implementing compile()
1124
+ */
1125
+ interface PoliciesBackend {
1126
+ load(ctx: PolicyScope): Result<PoliciesLoadResult | null>;
1127
+ save(ctx: PolicyScope, input: Readonly<{
1128
+ raw: unknown;
1129
+ etag?: string;
1130
+ }>): Result<Readonly<{
1131
+ updatedAt?: string;
1132
+ etag?: string;
1133
+ }>>;
1134
+ /** Optional server-side compile/validate (for shared editing / enforcement). */
1135
+ compile?(ctx: PolicyScope, input: Readonly<{
1136
+ raw: unknown;
1137
+ }>): Result<Readonly<{
1138
+ policies: readonly DynamicRule[];
1139
+ diagnostics: readonly PolicyDiagnostic[];
1140
+ }>>;
1141
+ /** Optional: reset/delete policies at scope (branch or workspace). */
1142
+ clear?(ctx: PolicyScope): Result<Readonly<{
1143
+ updatedAt?: string;
1144
+ }>>;
1145
+ }
1146
+ interface WorkspaceBackend {
1147
+ readonly info: WorkspaceInfo;
1148
+ readonly authors: AuthorsBackend;
1149
+ readonly permissions: PermissionsBackend;
1150
+ readonly branches: BranchesBackend;
1151
+ /** branch-scoped access control / participants */
1152
+ readonly access: BranchAccessBackend;
1153
+ /** first-class services channel */
1154
+ readonly services: ServicesBackend;
1155
+ readonly templates: TemplatesBackend;
1156
+ readonly snapshots: SnapshotsBackend;
1157
+ /** dynamic policies: workspace-scoped and/or branch-scoped rules */
1158
+ readonly policies: PoliciesBackend;
1159
+ readonly comments: CommentsBackend<CommentThread, CommentMessage, CommentAnchor>;
1160
+ }
1161
+
1162
+ type RetryOptions = {
1163
+ enabled?: boolean;
1164
+ maxAttempts?: number;
1165
+ baseDelayMs?: number;
1166
+ maxDelayMs?: number;
1167
+ jitter?: boolean;
1168
+ /** Run the first attempt immediately (no initial delay) */
1169
+ immediateFirst?: boolean;
1170
+ };
1171
+
1172
+ type CommentsDeps = {
1173
+ backend?: CommentsBackend<CommentThread, CommentMessage, CommentAnchor>;
1174
+ getScope?: () => BackendScope | undefined;
1175
+ retry?: RetryOptions;
1176
+ };
1177
+ declare class CommentsAPI {
1178
+ private threads;
1179
+ private bus;
1180
+ private deps;
1181
+ private retry;
1182
+ constructor(bus: EventBus<CanvasEvents>, deps?: CommentsDeps);
1183
+ private scope;
1184
+ private emitSync;
1185
+ loadAll(): Promise<void>;
1186
+ list(): CommentThread[];
1187
+ get(id: ThreadId): CommentThread | undefined;
1188
+ create(anchor: CommentAnchor, initialBody: string, meta?: Record<string, unknown>): Promise<ThreadId>;
1189
+ reply(threadId: ThreadId, body: string, meta?: Record<string, unknown>): Promise<CommentId>;
1190
+ editMessage(threadId: ThreadId, messageId: CommentId, body: string): Promise<void>;
1191
+ deleteMessage(threadId: ThreadId, messageId: CommentId): Promise<void>;
1192
+ move(threadId: ThreadId, anchor: CommentAnchor): Promise<void>;
1193
+ resolve(threadId: ThreadId, value?: boolean): Promise<void>;
1194
+ deleteThread(threadId: ThreadId): Promise<void>;
1195
+ retryJob(jobId: string): boolean;
1196
+ cancelJob(jobId: string): boolean;
1197
+ pendingJobs(): string[];
1198
+ private ensure;
1199
+ }
1200
+
1201
+ type CanvasBackend = {
1202
+ comments?: WorkspaceBackend["comments"];
1203
+ };
1204
+ type CanvasBackendOptions = {
1205
+ backend?: CanvasBackend;
1206
+ workspaceId?: string;
1207
+ } & Partial<BackendScope>;
1208
+
1209
+ type WireKind = "bind" | "include" | "exclude" | "service";
1210
+ type TagRef = {
1211
+ kind: "tag";
1212
+ id: string;
1213
+ };
1214
+ type FieldRef = {
1215
+ kind: "field";
1216
+ id: string;
1217
+ };
1218
+ type OptionRef = {
1219
+ kind: "option";
1220
+ fieldId: string;
1221
+ id: string;
1222
+ };
1223
+ type NodeRef = TagRef | FieldRef | OptionRef;
1224
+ type DuplicateOptions = {
1225
+ withChildren?: boolean;
1226
+ copyBindings?: boolean;
1227
+ copyIncludesExcludes?: boolean;
1228
+ copyOptionMaps?: boolean;
1229
+ id?: string;
1230
+ labelStrategy?: (old: string) => string;
1231
+ nameStrategy?: (old?: string) => string | undefined;
1232
+ optionIdStrategy?: (old: string) => string;
1233
+ };
1234
+ declare class Editor {
1235
+ private builder;
1236
+ private api;
1237
+ private readonly opts;
1238
+ private history;
1239
+ private index;
1240
+ private txnDepth;
1241
+ private txnLabel?;
1242
+ private stagedBefore?;
1243
+ private _lastPolicyDiagnostics?;
1244
+ constructor(builder: Builder, api: CanvasAPI, opts?: EditorOptions);
1245
+ getProps(): ServiceProps;
1246
+ transact(label: string, fn: () => void): void;
1247
+ exec(cmd: Command): void;
1248
+ undo(): boolean;
1249
+ redo(): boolean;
1250
+ clearService(id: string): void;
1251
+ duplicate(ref: NodeRef, opts?: DuplicateOptions): string;
1252
+ /**
1253
+ * Update the display label for a node and refresh the graph so node labels stay in sync.
1254
+ * Supports: tag ("t:*"), field ("f:*"), option ("o:*").
1255
+ * IDs are NOT changed; only the human-readable label.
1256
+ */
1257
+ reLabel(id: string, nextLabel: string): void;
1258
+ /**
1259
+ * Assign or change a field's `name`. Only allowed when the field (and its options) have NO service mapping.
1260
+ * - If `nextName` is empty/blank → removes the `name`.
1261
+ * - Emits an error if the field or any of its options carry a `service_id`.
1262
+ * - Emits an error if `nextName` collides with an existing field's name (case-sensitive).
1263
+ */
1264
+ setFieldName(fieldId: string, nextName: string | null | undefined): void;
1265
+ getLastPolicyDiagnostics(): PolicyDiagnostic[] | undefined;
1266
+ private duplicateTag;
1267
+ private duplicateField;
1268
+ private duplicateOption;
1269
+ private uniqueId;
1270
+ private uniqueOptionId;
1271
+ /**
1272
+ * Reorder a node:
1273
+ * - Tag: among its siblings (same bind_id) inside filters[]
1274
+ * - Field: inside order_for_tags[scopeTagId] (you must pass scopeTagId)
1275
+ * - Option: use placeOption() instead
1276
+ */
1277
+ placeNode(id: string, opts: {
1278
+ scopeTagId?: string;
1279
+ beforeId?: string;
1280
+ afterId?: string;
1281
+ index?: number;
1282
+ }): void;
1283
+ placeOption(optionId: string, opts: {
1284
+ beforeId?: string;
1285
+ afterId?: string;
1286
+ index?: number;
1287
+ }): void;
1288
+ addOption(fieldId: string, input: {
1289
+ id?: string;
1290
+ label: string;
1291
+ service_id?: number;
1292
+ pricing_role?: "base" | "utility" | "addon";
1293
+ [k: string]: any;
1294
+ }): string;
1295
+ updateOption(optionId: string, patch: Partial<{
1296
+ label: string;
1297
+ service_id: number;
1298
+ pricing_role: "base" | "utility" | "addon";
1299
+ } & Record<string, any>>): void;
1300
+ removeOption(optionId: string): void;
1301
+ editLabel(id: string, label: string): void;
1302
+ editName(fieldId: string, name: string | undefined): void;
1303
+ setService(id: string, input: {
1304
+ service_id?: number;
1305
+ pricing_role?: "base" | "utility";
1306
+ }): void;
1307
+ addTag(partial: Omit<Tag, "id" | "label"> & {
1308
+ id?: string;
1309
+ label: string;
1310
+ }): void;
1311
+ updateTag(id: string, patch: Partial<Tag>): void;
1312
+ removeTag(id: string): void;
1313
+ addField(partial: Omit<Field, "id" | "label" | "type"> & {
1314
+ id?: string;
1315
+ label: string;
1316
+ type: Field["type"];
1317
+ }): void;
1318
+ updateField(id: string, patch: Partial<Field>): void;
1319
+ removeField(id: string): void;
1320
+ remove(id: string): void;
1321
+ getNode(id: string): {
1322
+ kind: "tag";
1323
+ data?: Tag;
1324
+ owners: {
1325
+ parentTagId?: string;
1326
+ };
1327
+ } | {
1328
+ kind: "field";
1329
+ data?: Field;
1330
+ owners: {
1331
+ bindTagIds: string[];
1332
+ };
1333
+ } | {
1334
+ kind: "option";
1335
+ data?: any;
1336
+ owners: {
1337
+ fieldId?: string;
1338
+ };
1339
+ };
1340
+ getFieldQuantityRule(id: string): QuantityRule | undefined;
1341
+ setFieldQuantityRule(id: string, rule: unknown): void;
1342
+ clearFieldQuantityRule(id: string): void;
1343
+ /** Walk ancestors for a tag and detect if parent→child would create a cycle */
1344
+ private wouldCreateTagCycle;
1345
+ private wouldCreateIncludeExcludeCycle;
1346
+ include(receiverId: string, idOrIds: string | string[]): void;
1347
+ exclude(receiverId: string, idOrIds: string | string[]): void;
1348
+ connect(kind: WireKind, fromId: string, toId: string): void;
1349
+ disconnect(kind: WireKind, fromId: string, toId: string): void;
1350
+ setConstraint(tagId: string, flag: string, value: boolean | undefined): void;
1351
+ /**
1352
+ * Clear a constraint override by removing the local constraint that conflicts with an ancestor.
1353
+ */
1354
+ clearConstraintOverride(tagId: string, flag: string): void;
1355
+ /**
1356
+ * Clear a constraint from a tag and its descendants.
1357
+ * If a descendant has an override, it assigns that override's value as local.
1358
+ */
1359
+ clearConstraint(tagId: string, flag: string): void;
1360
+ private replaceProps;
1361
+ private patchProps;
1362
+ private afterMutation;
1363
+ private commit;
1364
+ private makeSnapshot;
1365
+ private loadSnapshot;
1366
+ private pushHistory;
1367
+ private genId;
1368
+ private emit;
1369
+ /**
1370
+ * Suggest/filter candidate services against the current visible-group
1371
+ * (single tag) context.
1372
+ *
1373
+ * - Excludes services already used in this group.
1374
+ * - Applies capability presence, tag constraints, rate policy, and compiled policies.
1375
+ *
1376
+ * @param candidates service ids to evaluate
1377
+ * @param ctx
1378
+ * @param ctx.tagId active visible-group tag id
1379
+ * @param ctx.usedServiceIds services already selected for this visible group (first is treated as "primary" for rate policy)
1380
+ * @param ctx.effectiveConstraints effective constraints for the active tag (dripfeed/refill/cancel)
1381
+ * @param ctx.policies raw JSON policies (will be compiled via compilePolicies)
1382
+ * @param ctx.fallback fallback/rate settings (defaults applied if omitted)
1383
+ */
1384
+ filterServicesForVisibleGroup(candidates: Array<number | string>, ctx: {
1385
+ tagId: string;
1386
+ usedServiceIds: Array<number | string>;
1387
+ effectiveConstraints?: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
1388
+ policies?: unknown;
1389
+ fallback?: FallbackSettings;
1390
+ }): ServiceCheck[];
1391
+ }
1392
+ type QuantityRule = {
1393
+ valueBy: "value" | "length" | "eval";
1394
+ code?: string;
1395
+ };
1396
+ type ServiceCheck = {
1397
+ id: number | string;
1398
+ ok: boolean;
1399
+ fitsConstraints: boolean;
1400
+ passesRate: boolean;
1401
+ passesPolicies: boolean;
1402
+ policyErrors?: string[];
1403
+ policyWarnings?: string[];
1404
+ reasons: Array<"constraint_mismatch" | "rate_policy" | "policy_error" | "missing_capability">;
1405
+ cap?: DgpServiceCapability;
1406
+ rate?: number;
1407
+ };
1408
+
1409
+ declare class CanvasAPI {
1410
+ private bus;
1411
+ private readonly state;
1412
+ readonly builder: Builder;
1413
+ readonly editor: Editor;
1414
+ private readonly autoEmit;
1415
+ readonly comments: CommentsAPI;
1416
+ readonly selection: Selection;
1417
+ constructor(builder: Builder, opts?: CanvasOptions & CanvasBackendOptions);
1418
+ on: <K extends keyof CanvasEvents>(event: K, handler: (payload: CanvasEvents[K]) => void) => () => void;
1419
+ once: <K extends keyof CanvasEvents>(event: K, handler: (payload: CanvasEvents[K]) => void) => () => void;
1420
+ emit<K extends keyof CanvasEvents>(event: K, payload: CanvasEvents[K]): void;
1421
+ snapshot(): CanvasState;
1422
+ getGraph(): GraphSnapshot;
1423
+ getSelection(): string[];
1424
+ getViewport(): Viewport;
1425
+ refreshGraph(): void;
1426
+ setPositions(pos: NodePositions): void;
1427
+ setPosition(id: string, x: number, y: number): void;
1428
+ select(ids: string[] | Set<string>): void;
1429
+ addToSelection(ids: string[] | Set<string>): void;
1430
+ toggleSelection(id: string): void;
1431
+ clearSelection(): void;
1432
+ selectComments(threadId?: string): void;
1433
+ setHighlighted(ids: string[] | Set<string>): void;
1434
+ setHover(id?: string): void;
1435
+ setViewport(v: Partial<Viewport>): void;
1436
+ startWire(from: string, kind: DraftWire["kind"]): void;
1437
+ previewWire(to?: string): void;
1438
+ commitWire(to: string): void;
1439
+ cancelWire(): void;
1440
+ private bump;
1441
+ dispose(): void;
1442
+ undo(): void;
1443
+ private edgeRel;
1444
+ getEdgeRel(): EdgeKind;
1445
+ setEdgeRel(rel: EdgeKind): void;
1446
+ /** Internal mirror of which fields should show their options as nodes. */
1447
+ private shownOptionFields;
1448
+ /** Return the field ids whose options are currently set to be visible as nodes. */
1449
+ getShownOptionFields(): string[];
1450
+ /** True if this field’s options are shown as nodes. */
1451
+ isFieldOptionsShown(fieldId: string): boolean;
1452
+ /**
1453
+ * Set visibility of option nodes for a field, then rebuild the graph.
1454
+ * When shown = true, the Builder will emit option nodes for this field.
1455
+ */
1456
+ setFieldOptionsShown(fieldId: string, shown: boolean): void;
1457
+ /** Toggle option-node visibility for a field. Returns the new visibility. */
1458
+ toggleFieldOptions(fieldId: string): boolean;
1459
+ /**
1460
+ * Replace the whole set of fields whose options are visible as nodes.
1461
+ * Useful for restoring a saved UI state.
1462
+ */
1463
+ setShownOptionFields(ids: Iterable<string>): void;
1464
+ getConstraints(): {
1465
+ id: string;
1466
+ label: string;
1467
+ description: string;
1468
+ value: string;
1469
+ }[];
1470
+ getServiceProps(): ServiceProps;
1471
+ }
1472
+
1473
+ type FormSnapshot = {
1474
+ values: Record<string, Scalar | Scalar[]>;
1475
+ selections: Record<string, string[]>;
1476
+ };
1477
+ type FormApi = {
1478
+ /** Scalar/array value by fieldId (non-option inputs) */
1479
+ get: (fieldId: string) => Scalar | Scalar[] | undefined;
1480
+ set: (fieldId: string, value: Scalar | Scalar[]) => void;
1481
+ /** Option selections by fieldId (array of optionIds) */
1482
+ getSelections: (fieldId: string) => string[];
1483
+ setSelections: (fieldId: string, optionIds: string[]) => void;
1484
+ toggleSelection: (fieldId: string, optionId: string) => void;
1485
+ /** Read-only snapshot for debugging */
1486
+ snapshot: () => FormSnapshot;
1487
+ /** Simple subscribe (re-render triggers) */
1488
+ subscribe: (fn: () => void) => () => void;
1489
+ };
1490
+ declare function FormProvider({ initial, children, }: {
1491
+ initial?: Partial<FormSnapshot>;
1492
+ children: ReactNode;
1493
+ }): react_jsx_runtime.JSX.Element;
1494
+ /** Strict hook (throws if no provider) */
1495
+ declare function useFormApi(): FormApi;
1496
+ /** Optional hook (returns null if no provider) */
1497
+ declare function useOptionalFormApi(): FormApi | null;
1498
+ /** Field-scoped helpers */
1499
+ declare function useFormField(fieldId: string): {
1500
+ value: Scalar | Scalar[] | undefined;
1501
+ set: (value: Scalar | Scalar[]) => void;
1502
+ };
1503
+ declare function useFormSelections(fieldId: string): {
1504
+ selected: string[];
1505
+ set: (optionIds: string[]) => void;
1506
+ toggle: (optionId: string) => void;
1507
+ };
1508
+
1509
+ /** Matches your InputWrapper’s expectations */
1510
+ type InputKind = string;
1511
+ type InputVariant = 'default' | (string & {});
1512
+ type InputAdapter = {
1513
+ /** Prop name where the value goes on the host component (default: "value") */
1514
+ valueProp?: string;
1515
+ /** Prop name of the change handler on the host component (default: "onChange") */
1516
+ changeProp?: string;
1517
+ /**
1518
+ * Normalize the host's change payload into a Scalar | Scalar[] your form will store.
1519
+ * If omitted, `next as Scalar | Scalar[]` is used.
1520
+ */
1521
+ getValue?: (next: unknown, prev: unknown) => Scalar | Scalar[];
1522
+ };
1523
+ type InputDescriptor = {
1524
+ Component: React.ComponentType<Record<string, unknown>>;
1525
+ adapter?: InputAdapter;
1526
+ defaultProps?: Record<string, unknown>;
1527
+ };
1528
+ type VariantMap = Map<InputVariant, InputDescriptor>;
1529
+ type RegistryStore = Map<InputKind, VariantMap>;
1530
+ type Registry = {
1531
+ get(kind: InputKind, variant?: InputVariant): InputDescriptor | undefined;
1532
+ register(kind: InputKind, descriptor: InputDescriptor, variant?: InputVariant): void;
1533
+ unregister(kind: InputKind, variant?: InputVariant): void;
1534
+ registerMany(entries: Array<{
1535
+ kind: InputKind;
1536
+ descriptor: InputDescriptor;
1537
+ variant?: InputVariant;
1538
+ }>): void;
1539
+ /** low-level escape hatch */
1540
+ _store: RegistryStore;
1541
+ };
1542
+ declare function createInputRegistry(): Registry;
1543
+ /** Helper used by InputWrapper */
1544
+ declare function resolveInputDescriptor(registry: Registry, kind: InputKind, variant?: InputVariant): InputDescriptor | undefined;
1545
+
1546
+ type InputsCtxValue = {
1547
+ registry: Registry;
1548
+ register: (kind: InputKind, descriptor: InputDescriptor, variant?: InputVariant) => void;
1549
+ unregister: (kind: InputKind, variant?: InputVariant) => void;
1550
+ registerMany: (entries: Array<{
1551
+ kind: InputKind;
1552
+ descriptor: InputDescriptor;
1553
+ variant?: InputVariant;
1554
+ }>) => void;
1555
+ };
1556
+ declare function Provider({ children, initialRegistry, }: {
1557
+ children: ReactNode;
1558
+ /** Optional pre-built registry (e.g., you registered built-ins/customs before mounting) */
1559
+ initialRegistry?: Registry;
1560
+ }): react_jsx_runtime.JSX.Element;
1561
+ declare function useInputs(): InputsCtxValue;
1562
+
1563
+ type InputWrapperProps = {
1564
+ field: Field;
1565
+ disabled?: boolean;
1566
+ /** Extra props to forward to the host component (low priority, overridden by adapter wiring). */
1567
+ extraProps?: Record<string, unknown>;
1568
+ };
1569
+ type OnChangeValue = ButtonValue | ButtonValue[];
1570
+ declare function Wrapper({ field, disabled, extraProps, }: InputWrapperProps): react_jsx_runtime.JSX.Element | null;
1571
+
1572
+ export { type AdminPolicies, type BaseFieldUI, type ButtonValue, CanvasAPI, type CanvasEvents, type CanvasOptions, type CanvasState, type Command, type CommentAnchor, type CommentId, type CommentMessage, type CommentNode, type CommentThread, type ConnectKind, type ConstraintKey, type DgpServiceCapability, type DgpServiceMap, type DraftWire, type DynamicRule, type EdgeKind, type EdgeRoute, type EdgeView, type EditorEvents, type EditorOptions, type EditorSnapshot, EventBus, type EventMap, type FallbackDiagnostics, type FallbackSettings, type Field, type FieldOption, type FieldType, type FieldWithTypedDefaults, type FlagKey, type FlowNode, type FormApi, FormProvider, type FormSnapshot, type GraphEdge, type GraphNode, type GraphSnapshot, type IdType, type InputAdapter, type InputDescriptor, type InputKind, type InputVariant, type InputWrapperProps, type LayoutState, type NodeIdRef, type NodeKind, type NodePos, type NodePositions, type NodeView, type OnChangeValue, type OrderSnapshot, type PricingRole, Provider, type QuantityMark, type QuantityRule$1 as QuantityRule, type RatePolicy, type Registry, type Scalar, type ServiceEstimates, type ServiceFallback, type ServiceFallbacks, type ServiceFlag, type ServiceFlags, type ServiceIdRef, type ServiceProps, type ServiceWhereClause, type ServiceWhereOp, type SnapshotContext, type SpeedEstimate, type Tag, type ThreadId, type TimeRangeEstimate, type Ui, type UiAnyOf, type UiArray, type UiBoolean, type UiNumber, type UiObject, type UiString, type UiValue, type UtilityLineItem, type UtilityMark, type UtilityMode, type ValidationCode, type ValidationError, type ValidatorOptions, type Viewport, type WithQuantityDefault, Wrapper, createInputRegistry, resolveInputDescriptor, useFormApi, useFormField, useFormSelections, useInputs, useOptionalFormApi };