@timeax/digital-service-engine 0.0.3 → 0.0.5

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.
@@ -1,7 +1,313 @@
1
- import { NodeProps } from 'reactflow';
2
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
2
  import React, { ReactNode } from 'react';
4
3
 
4
+ type EventMap = Record<string, unknown>;
5
+ declare class EventBus<E extends EventMap> {
6
+ private listeners;
7
+ on<K extends keyof E>(event: K, handler: (payload: E[K]) => void): () => void;
8
+ once<K extends keyof E>(event: K, handler: (payload: E[K]) => void): () => void;
9
+ emit<K extends keyof E>(event: K, payload: E[K]): void;
10
+ clear(): void;
11
+ }
12
+
13
+ interface ButtonValue {
14
+ id: string;
15
+ value: string | number;
16
+ service_id?: number;
17
+ pricing_role?: "base" | "utility";
18
+ meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
19
+ }
20
+ type Scalar = string | number | boolean | ButtonValue | null;
21
+ type UtilityMode = "flat" | "per_quantity" | "per_value" | "percent";
22
+ type QuantityRule$1 = {
23
+ valueBy: "value" | "length" | "eval";
24
+ code?: string;
25
+ };
26
+ type UtilityLineItem = {
27
+ nodeId: string;
28
+ mode: UtilityMode;
29
+ rate: number;
30
+ inputs: {
31
+ quantity: number;
32
+ value?: Scalar | Scalar[];
33
+ valueBy?: "value" | "length" | "eval";
34
+ evalCodeUsed?: boolean;
35
+ };
36
+ };
37
+ type ServiceFallbacks = ServiceFallback;
38
+ type FallbackDiagnostics = {
39
+ scope: "node" | "global";
40
+ nodeId?: string;
41
+ primary: string | number;
42
+ candidate: string | number;
43
+ reasons: Array<"rate_violation" | "constraint_mismatch" | "unknown_service" | "ambiguous_context">;
44
+ };
45
+ type SnapshotContext = {
46
+ /** The single active tag id for this order */
47
+ tag: string;
48
+ /** Effective (post-propagation) constraints on that tag */
49
+ constraints: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
50
+ /**
51
+ * Per-node evaluation context:
52
+ * - For the active tag node itself: the same tag id.
53
+ * - For an option node: parent's field.bind_id must include this tag to be applicable; otherwise null.
54
+ * - For a field node (optional to include later): same rule as option, derived from field.bind_id.
55
+ */
56
+ nodeContexts: Record<string, string | null>;
57
+ /** Client pruning policy used (so server can mirror/compare). */
58
+ policy: {
59
+ ratePolicy: {
60
+ kind: "lte_primary" | "none";
61
+ thresholdPct?: number;
62
+ };
63
+ requireConstraintFit: boolean;
64
+ };
65
+ };
66
+ type OrderSnapshot = {
67
+ version: "1";
68
+ mode: "prod" | "dev";
69
+ builtAt: string;
70
+ selection: {
71
+ tag: string;
72
+ buttons: string[];
73
+ fields: Array<{
74
+ id: string;
75
+ type: string;
76
+ selectedOptions?: string[];
77
+ }>;
78
+ };
79
+ inputs: {
80
+ form: Record<string, Scalar | Scalar[]>;
81
+ selections: Record<string, string[]>;
82
+ };
83
+ quantity: number;
84
+ quantitySource: {
85
+ kind: "field" | "tag" | "option" | "default";
86
+ id?: string;
87
+ rule?: QuantityRule$1;
88
+ defaultedFromHost?: boolean;
89
+ };
90
+ min: number;
91
+ max: number;
92
+ services: Array<string | number>;
93
+ serviceMap: Record<string, Array<string | number>>;
94
+ fallbacks?: ServiceFallbacks;
95
+ utilities?: UtilityLineItem[];
96
+ warnings?: {
97
+ utility?: Array<{
98
+ nodeId: string;
99
+ reason: string;
100
+ }>;
101
+ fallbacks?: FallbackDiagnostics[];
102
+ };
103
+ meta?: {
104
+ schema_version?: string;
105
+ workspaceId?: string;
106
+ builder?: {
107
+ commit?: string;
108
+ };
109
+ context?: SnapshotContext;
110
+ };
111
+ };
112
+
113
+ type TimeRangeEstimate = {
114
+ min_seconds?: number;
115
+ max_seconds?: number;
116
+ label?: string;
117
+ meta?: Record<string, unknown>;
118
+ };
119
+ type SpeedEstimate = {
120
+ amount?: number;
121
+ per?: "minute" | "hour" | "day" | "week" | "month";
122
+ unit?: string;
123
+ label?: string;
124
+ meta?: Record<string, unknown>;
125
+ };
126
+ type ServiceEstimates = {
127
+ start?: TimeRangeEstimate;
128
+ speed?: SpeedEstimate;
129
+ average?: TimeRangeEstimate;
130
+ meta?: Record<string, unknown>;
131
+ };
132
+ type ServiceFlag = {
133
+ enabled: boolean;
134
+ description: string;
135
+ meta?: Record<string, unknown>;
136
+ };
137
+ type IdType = string | number;
138
+ type ServiceFlags = Record<string, ServiceFlag>;
139
+ type DgpServiceCapability = {
140
+ id: IdType;
141
+ name?: string;
142
+ rate: number;
143
+ min?: number;
144
+ max?: number;
145
+ category?: string;
146
+ flags?: ServiceFlags;
147
+ estimates?: ServiceEstimates;
148
+ meta?: Record<string, unknown>;
149
+ [x: string]: any;
150
+ };
151
+ type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
152
+
153
+ type NodeRef$1 = {
154
+ kind: "tag";
155
+ id: string;
156
+ node: Tag;
157
+ } | {
158
+ kind: "field";
159
+ id: string;
160
+ node: Field;
161
+ } | {
162
+ kind: "option";
163
+ id: string;
164
+ node: FieldOption;
165
+ fieldId: string;
166
+ };
167
+ type NodeMap = Map<string, NodeRef$1>;
168
+
169
+ 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";
170
+ type ValidationError = {
171
+ code: ValidationCode;
172
+ message: string;
173
+ severity: "error" | "warning" | "info";
174
+ nodeId?: string;
175
+ details?: Record<string, unknown> & {
176
+ affectedIds?: string[];
177
+ };
178
+ };
179
+ type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
180
+ /**
181
+ * Host-extensible service filter clause.
182
+ * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
183
+ */
184
+ type ServiceWhereClause = {
185
+ path: string;
186
+ op?: ServiceWhereOp;
187
+ value?: unknown;
188
+ };
189
+ type DynamicRule = {
190
+ id: string;
191
+ label?: string;
192
+ scope: "global" | "visible_group";
193
+ subject: "services";
194
+ /**
195
+ * Package-level filter surface:
196
+ * - role/tag/field are core to the builder schema
197
+ * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
198
+ */
199
+ filter?: {
200
+ role?: "base" | "utility" | "both";
201
+ tag_id?: string | string[];
202
+ field_id?: string | string[];
203
+ where?: ServiceWhereClause[];
204
+ };
205
+ /**
206
+ * Projection is intentionally open:
207
+ * hosts may project custom service properties.
208
+ */
209
+ projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
210
+ op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
211
+ value?: number | boolean;
212
+ severity?: "error" | "warning";
213
+ message?: string;
214
+ };
215
+ type ValidatorOptions = {
216
+ serviceMap?: DgpServiceMap;
217
+ nodeMap?: NodeMap;
218
+ allowUnsafe?: boolean;
219
+ selectedOptionKeys?: string[];
220
+ globalUtilityGuard?: boolean;
221
+ policies?: DynamicRule[];
222
+ fallbackSettings?: FallbackSettings;
223
+ };
224
+ type RatePolicy = {
225
+ kind: "lte_primary";
226
+ } | {
227
+ kind: "within_pct";
228
+ pct: number;
229
+ } | {
230
+ kind: "at_least_pct_lower";
231
+ pct: number;
232
+ };
233
+ type FallbackSettings = {
234
+ /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
235
+ requireConstraintFit?: boolean;
236
+ /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
237
+ ratePolicy?: RatePolicy;
238
+ /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
239
+ selectionStrategy?: "priority" | "cheapest";
240
+ /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
241
+ mode?: "strict" | "dev";
242
+ };
243
+
244
+ type ServiceIdRef = number | string;
245
+ type NodeIdRef = string;
246
+ type ServiceFallback = {
247
+ /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
248
+ nodes?: Record<NodeIdRef, ServiceIdRef[]>;
249
+ /** Primary→fallback list used when no node-scoped entry is present */
250
+ global?: Record<ServiceIdRef, ServiceIdRef[]>;
251
+ };
252
+ type FallbackRegistrationScope = "global" | "node";
253
+ type FallbackScopeRef = {
254
+ scope: "global";
255
+ primary: ServiceIdRef;
256
+ } | {
257
+ scope: "node";
258
+ nodeId: NodeIdRef;
259
+ };
260
+ type FallbackRegistration = {
261
+ scope: FallbackRegistrationScope;
262
+ /**
263
+ * For node scope => node id
264
+ * For global scope => omitted
265
+ */
266
+ scopeId?: NodeIdRef;
267
+ /**
268
+ * The primary DGP service this registration belongs to.
269
+ * For global scope, this is the global key.
270
+ * For node scope, this is resolved from ServiceProps/snapshot context.
271
+ */
272
+ primary: ServiceIdRef;
273
+ /** Registered fallback services */
274
+ services: ServiceIdRef[];
275
+ };
276
+ type FallbackCheckReason = "duplicate" | "self_reference" | "unknown_primary" | "unknown_candidate" | "missing_snapshot" | "node_scope_not_supported" | "node_primary_unresolved" | "ambiguous_context" | "invalid_candidate" | "unknown_service" | "no_primary" | "rate_violation" | "constraint_mismatch" | "cycle" | "no_tag_context" | "missing_service_props" | "node_not_found";
277
+ type FallbackCandidateCheck = {
278
+ candidate: ServiceIdRef;
279
+ ok: boolean;
280
+ reasons: FallbackCheckReason[];
281
+ };
282
+ type FallbackCheckResult = {
283
+ context: FallbackScopeRef;
284
+ /**
285
+ * Resolved primary when known.
286
+ * For global scope this should normally equal context.primary.
287
+ */
288
+ primary?: ServiceIdRef;
289
+ allowed: ServiceIdRef[];
290
+ rejected: FallbackCandidateCheck[];
291
+ warnings: FallbackCheckReason[];
292
+ };
293
+ type FallbackEditorState = {
294
+ original: ServiceFallback;
295
+ current: ServiceFallback;
296
+ changed: boolean;
297
+ };
298
+ type FallbackMutationOptions = {
299
+ /**
300
+ * When true, reject candidates failing validation.
301
+ * When false, keep structurally valid values and return warnings.
302
+ */
303
+ strict?: boolean;
304
+ /**
305
+ * Optional insert position for add/addMany.
306
+ * Omit to append.
307
+ */
308
+ index?: number;
309
+ };
310
+
5
311
  type PricingRole = "base" | "utility";
6
312
  type FieldType = "custom" | (string & {});
7
313
  /** ── Marker types (live inside meta; non-breaking) ───────────────────── */
@@ -106,46 +412,6 @@ interface UiObject {
106
412
  required?: string[];
107
413
  order?: string[];
108
414
  }
109
- /** ---------------- Typed defaults helpers ---------------- */
110
- /**
111
- * UiValue<U>: given a Ui node U, infer the runtime value type.
112
- */
113
- type UiValue<U extends Ui> = U extends {
114
- type: "string";
115
- } ? string : U extends {
116
- type: "number";
117
- } ? number : U extends {
118
- type: "boolean";
119
- } ? boolean : U extends {
120
- type: "anyOf";
121
- multiple: true;
122
- } ? Array<U["items"][number]["value"]> : U extends {
123
- type: "anyOf";
124
- } ? U["items"][number]["value"] : U extends {
125
- type: "array";
126
- item: infer I extends Ui;
127
- } ? Array<UiValue<I>> : U extends {
128
- type: "array";
129
- items: infer T extends Ui[];
130
- } ? {
131
- [K in keyof T]: UiValue<T[K]>;
132
- } : U extends {
133
- type: "object";
134
- fields: infer F extends Record<string, Ui>;
135
- } ? {
136
- [K in keyof F]?: UiValue<F[K]>;
137
- } : unknown;
138
- /**
139
- * FieldWithTypedDefaults<T>: same shape as BaseFieldUI, but:
140
- * - ui is a concrete map T (propName → Ui node)
141
- * - defaults are auto-typed from T via UiValue
142
- */
143
- type FieldWithTypedDefaults<T extends Record<string, Ui>> = Omit<BaseFieldUI, "ui" | "defaults"> & {
144
- ui: T;
145
- defaults?: Partial<{
146
- [K in keyof T]: UiValue<T[K]>;
147
- }>;
148
- };
149
415
  type FieldOption = {
150
416
  id: string;
151
417
  label: string;
@@ -174,11 +440,6 @@ type Field = BaseFieldUI & {
174
440
  service_id?: number;
175
441
  } & WithQuantityDefault));
176
442
  type ConstraintKey = string;
177
- /**
178
- * Back-compat alias: older code may still import FlagKey.
179
- * Keeping this prevents a wave of TS errors while still allowing any string key.
180
- */
181
- type FlagKey = ConstraintKey;
182
443
  type Tag = {
183
444
  id: string;
184
445
  label: string;
@@ -214,14 +475,6 @@ type ServiceProps = {
214
475
  name?: string;
215
476
  notices?: ServicePropsNotice[];
216
477
  };
217
- type ServiceIdRef = number | string;
218
- type NodeIdRef = string;
219
- type ServiceFallback = {
220
- /** Node-scoped fallbacks: prefer these when that node’s primary service fails */
221
- nodes?: Record<NodeIdRef, ServiceIdRef[]>;
222
- /** Primary→fallback list used when no node-scoped entry is present */
223
- global?: Record<ServiceIdRef, ServiceIdRef[]>;
224
- };
225
478
  type NoticeType = "public" | "private";
226
479
  type NoticeSeverity = "info" | "warning" | "error";
227
480
  /**
@@ -270,10 +523,6 @@ type GraphSnapshot = {
270
523
  nodes: GraphNode[];
271
524
  edges: GraphEdge[];
272
525
  };
273
- type FlowNode = NodeProps<{
274
- node: GraphNode;
275
- [x: string]: any;
276
- }>;
277
526
 
278
527
  type CommentId = string;
279
528
  type ThreadId = string;
@@ -391,193 +640,58 @@ type CanvasEvents = {
391
640
  "comment:select": {
392
641
  threadId?: string;
393
642
  };
394
- "edge:change": EdgeKind;
395
- "comment:sync": {
396
- op: "create_thread" | "add_message" | "edit_message" | "delete_message" | "move_thread" | "resolve_thread" | "delete_thread";
397
- threadId: string;
398
- messageId?: string;
399
- status: "scheduled" | "retrying" | "succeeded" | "failed" | "cancelled";
400
- attempt: number;
401
- nextDelayMs?: number;
402
- error?: any;
403
- };
404
- };
405
- type NodeView = GraphNode & {
406
- position?: NodePos;
407
- };
408
- type EdgeView = GraphEdge;
409
- type CanvasOptions = {
410
- initialViewport?: Partial<Viewport>;
411
- autoEmitState?: boolean;
412
- };
413
-
414
- type CommentNode = {
415
- id: string;
416
- text: string;
417
- status: "open" | "resolved";
418
- anchor?: {
419
- kind: "tag" | "field" | "option";
420
- id: string;
421
- };
422
- replies?: Array<{
423
- id: string;
424
- text: string;
425
- created_at: string;
426
- author?: string;
427
- }>;
428
- xy?: {
429
- x: number;
430
- y: number;
431
- };
432
- meta?: Record<string, unknown>;
433
- };
434
- type EdgeRoute = {
435
- id: string;
436
- points: Array<{
437
- x: number;
438
- y: number;
439
- }>;
440
- };
441
- type LayoutState = {
442
- canvas: CanvasState;
443
- edges?: EdgeRoute[];
444
- };
445
- type EditorSnapshot = {
446
- props: ServiceProps;
447
- layout?: LayoutState;
448
- comments?: CommentNode[];
449
- meta?: Record<string, unknown>;
450
- };
451
-
452
- type TimeRangeEstimate = {
453
- min_seconds?: number;
454
- max_seconds?: number;
455
- label?: string;
456
- meta?: Record<string, unknown>;
457
- };
458
- type SpeedEstimate = {
459
- amount?: number;
460
- per?: "minute" | "hour" | "day" | "week" | "month";
461
- unit?: string;
462
- label?: string;
463
- meta?: Record<string, unknown>;
464
- };
465
- type ServiceEstimates = {
466
- start?: TimeRangeEstimate;
467
- speed?: SpeedEstimate;
468
- average?: TimeRangeEstimate;
469
- meta?: Record<string, unknown>;
470
- };
471
- type ServiceFlag = {
472
- enabled: boolean;
473
- description: string;
474
- meta?: Record<string, unknown>;
475
- };
476
- type IdType = string | number;
477
- type ServiceFlags = Record<string, ServiceFlag>;
478
- type DgpServiceCapability = {
479
- id: IdType;
480
- name?: string;
481
- rate: number;
482
- min?: number;
483
- max?: number;
484
- category?: string;
485
- flags?: ServiceFlags;
486
- estimates?: ServiceEstimates;
487
- meta?: Record<string, unknown>;
488
- [x: string]: any;
489
- };
490
- type DgpServiceMap = Record<string, DgpServiceCapability> & Record<number, DgpServiceCapability>;
491
-
492
- type NodeRef$1 = {
493
- kind: "tag";
494
- id: string;
495
- node: Tag;
496
- } | {
497
- kind: "field";
498
- id: string;
499
- node: Field;
500
- } | {
501
- kind: "option";
502
- id: string;
503
- node: FieldOption;
504
- fieldId: string;
505
- };
506
- type NodeMap = Map<string, NodeRef$1>;
507
-
508
- 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";
509
- type ValidationError = {
510
- code: ValidationCode;
511
- message: string;
512
- severity: "error" | "warning" | "info";
513
- nodeId?: string;
514
- details?: Record<string, unknown> & {
515
- affectedIds?: string[];
516
- };
643
+ "edge:change": EdgeKind;
644
+ "comment:sync": {
645
+ op: "create_thread" | "add_message" | "edit_message" | "delete_message" | "move_thread" | "resolve_thread" | "delete_thread";
646
+ threadId: string;
647
+ messageId?: string;
648
+ status: "scheduled" | "retrying" | "succeeded" | "failed" | "cancelled";
649
+ attempt: number;
650
+ nextDelayMs?: number;
651
+ error?: any;
652
+ };
517
653
  };
518
- type ServiceWhereOp = "eq" | "neq" | "in" | "nin" | "exists" | "truthy" | "falsy" | "sw";
519
- /**
520
- * Host-extensible service filter clause.
521
- * `path` should usually be "service.<prop>" or "service.meta.<prop>" etc.
522
- */
523
- type ServiceWhereClause = {
524
- path: string;
525
- op?: ServiceWhereOp;
526
- value?: unknown;
654
+ type CanvasOptions = {
655
+ initialViewport?: Partial<Viewport>;
656
+ autoEmitState?: boolean;
527
657
  };
528
- type DynamicRule = {
658
+
659
+ type CommentNode = {
529
660
  id: string;
530
- label?: string;
531
- scope: "global" | "visible_group";
532
- subject: "services";
533
- /**
534
- * Package-level filter surface:
535
- * - role/tag/field are core to the builder schema
536
- * - where[] allows hosts to filter against extra service properties (handler_id/platform_id/type/key/category_id/etc.)
537
- */
538
- filter?: {
539
- role?: "base" | "utility" | "both";
540
- tag_id?: string | string[];
541
- field_id?: string | string[];
542
- where?: ServiceWhereClause[];
661
+ text: string;
662
+ status: "open" | "resolved";
663
+ anchor?: {
664
+ kind: "tag" | "field" | "option";
665
+ id: string;
543
666
  };
544
- /**
545
- * Projection is intentionally open:
546
- * hosts may project custom service properties.
547
- */
548
- projection?: "service.id" | "service.name" | "service.rate" | "service.min" | "service.max" | "service.category" | string;
549
- op: "all_equal" | "unique" | "no_mix" | "all_true" | "any_true" | "max_count" | "min_count";
550
- value?: number | boolean;
551
- severity?: "error" | "warning";
552
- message?: string;
667
+ replies?: Array<{
668
+ id: string;
669
+ text: string;
670
+ created_at: string;
671
+ author?: string;
672
+ }>;
673
+ xy?: {
674
+ x: number;
675
+ y: number;
676
+ };
677
+ meta?: Record<string, unknown>;
553
678
  };
554
- type ValidatorOptions = {
555
- serviceMap?: DgpServiceMap;
556
- nodeMap?: NodeMap;
557
- allowUnsafe?: boolean;
558
- selectedOptionKeys?: string[];
559
- globalUtilityGuard?: boolean;
560
- policies?: DynamicRule[];
561
- fallbackSettings?: FallbackSettings;
679
+ type EdgeRoute = {
680
+ id: string;
681
+ points: Array<{
682
+ x: number;
683
+ y: number;
684
+ }>;
562
685
  };
563
- type RatePolicy = {
564
- kind: "lte_primary";
565
- } | {
566
- kind: "within_pct";
567
- pct: number;
568
- } | {
569
- kind: "at_least_pct_lower";
570
- pct: number;
686
+ type LayoutState = {
687
+ canvas: CanvasState;
688
+ edges?: EdgeRoute[];
571
689
  };
572
- type FallbackSettings = {
573
- /** Require fallbacks to satisfy tag constraints (dripfeed/refill/cancel) when a tag context is known. Default: true */
574
- requireConstraintFit?: boolean;
575
- /** Rate rule policy. Default: { kind: 'lte_primary' } i.e. candidate.rate <= primary.rate */
576
- ratePolicy?: RatePolicy;
577
- /** When multiple candidates remain, choose first (priority) or cheapest. Default: 'priority' */
578
- selectionStrategy?: "priority" | "cheapest";
579
- /** Validation mode: 'strict' → node-scoped violations reported as ValidationError; 'dev' → only collect diagnostics. Default: 'strict' */
580
- mode?: "strict" | "dev";
690
+ type EditorSnapshot = {
691
+ props: ServiceProps;
692
+ layout?: LayoutState;
693
+ comments?: CommentNode[];
694
+ meta?: Record<string, unknown>;
581
695
  };
582
696
 
583
697
  /** Options you can set on the builder (used for validation/visibility) */
@@ -628,111 +742,38 @@ interface Builder {
628
742
  getNodeMap(): NodeMap;
629
743
  }
630
744
 
631
- interface ButtonValue {
632
- id: string;
633
- value: string | number;
634
- service_id?: number;
635
- pricing_role?: "base" | "utility";
636
- meta?: Record<string, unknown> & UtilityMark & WithQuantityDefault;
637
- }
638
- type Scalar = string | number | boolean | ButtonValue | null;
639
- type UtilityMode = "flat" | "per_quantity" | "per_value" | "percent";
640
- type QuantityRule$1 = {
641
- valueBy: "value" | "length" | "eval";
642
- code?: string;
643
- };
644
- type UtilityLineItem = {
645
- nodeId: string;
646
- mode: UtilityMode;
647
- rate: number;
648
- inputs: {
649
- quantity: number;
650
- value?: Scalar | Scalar[];
651
- valueBy?: "value" | "length" | "eval";
652
- evalCodeUsed?: boolean;
653
- };
654
- };
655
- type ServiceFallbacks = {
656
- nodes?: Record<string, Array<string | number>>;
657
- global?: Record<string | number, Array<string | number>>;
658
- };
659
- type FallbackDiagnostics = {
660
- scope: "node" | "global";
661
- nodeId?: string;
662
- primary: string | number;
663
- candidate: string | number;
664
- reasons: Array<"rate_violation" | "constraint_mismatch" | "unknown_service" | "ambiguous_context">;
665
- };
666
- type SnapshotContext = {
667
- /** The single active tag id for this order */
668
- tag: string;
669
- /** Effective (post-propagation) constraints on that tag */
670
- constraints: Partial<Record<"refill" | "cancel" | "dripfeed", boolean>>;
745
+ /**
746
+ * Keep the editor contract exactly as discussed:
747
+ * - mutates only ServiceFallback (internal clone)
748
+ * - props are read-only context
749
+ * - get(serviceId) is service-centric
750
+ * - getScope(context) is raw scope access
751
+ */
752
+ interface FallbackEditor$1 {
753
+ state(): FallbackEditorState;
754
+ value(): ServiceFallback;
755
+ reset(): FallbackEditorState;
756
+ /** Service-centric: all registrations that belong to this primary service */
757
+ get(serviceId: ServiceIdRef): FallbackRegistration[];
758
+ /** Exact/raw scope access */
759
+ getScope(context: FallbackScopeRef): ServiceIdRef[];
760
+ /** Validation preview */
761
+ check(context: FallbackScopeRef, candidates?: ServiceIdRef[]): FallbackCheckResult;
762
+ add(context: FallbackScopeRef, candidate: ServiceIdRef, options?: FallbackMutationOptions): FallbackEditorState;
763
+ addMany(context: FallbackScopeRef, candidates: ServiceIdRef[], options?: FallbackMutationOptions): FallbackEditorState;
764
+ remove(context: FallbackScopeRef, candidate: ServiceIdRef): FallbackEditorState;
765
+ replace(context: FallbackScopeRef, candidates: ServiceIdRef[], options?: FallbackMutationOptions): FallbackEditorState;
766
+ clear(context: FallbackScopeRef): FallbackEditorState;
671
767
  /**
672
- * Per-node evaluation context:
673
- * - For the active tag node itself: the same tag id.
674
- * - For an option node: parent's field.bind_id must include this tag to be applicable; otherwise null.
675
- * - For a field node (optional to include later): same rule as option, derived from field.bind_id.
768
+ * Optional helper for picker UIs:
769
+ * shows candidates that the core fallback resolver would currently accept.
676
770
  */
677
- nodeContexts: Record<string, string | null>;
678
- /** Client pruning policy used (so server can mirror/compare). */
679
- policy: {
680
- ratePolicy: {
681
- kind: "lte_primary" | "none";
682
- thresholdPct?: number;
683
- };
684
- requireConstraintFit: boolean;
685
- };
686
- };
687
- type OrderSnapshot = {
688
- version: "1";
689
- mode: "prod" | "dev";
690
- builtAt: string;
691
- selection: {
692
- tag: string;
693
- buttons: string[];
694
- fields: Array<{
695
- id: string;
696
- type: string;
697
- selectedOptions?: string[];
698
- }>;
699
- };
700
- inputs: {
701
- form: Record<string, Scalar | Scalar[]>;
702
- selections: Record<string, string[]>;
703
- };
704
- quantity: number;
705
- quantitySource: {
706
- kind: "field" | "tag" | "option" | "default";
707
- id?: string;
708
- rule?: QuantityRule$1;
709
- defaultedFromHost?: boolean;
710
- };
711
- min: number;
712
- max: number;
713
- services: Array<string | number>;
714
- serviceMap: Record<string, Array<string | number>>;
715
- fallbacks?: {
716
- nodes?: Record<string, Array<string | number>>;
717
- global?: Record<string | number, Array<string | number>>;
718
- };
719
- utilities?: UtilityLineItem[];
720
- warnings?: {
721
- utility?: Array<{
722
- nodeId: string;
723
- reason: string;
724
- }>;
725
- fallbacks?: FallbackDiagnostics[];
726
- };
727
- meta?: {
728
- schema_version?: string;
729
- workspaceId?: string;
730
- builder?: {
731
- commit?: string;
732
- };
733
- context?: SnapshotContext;
734
- };
735
- };
771
+ eligible(context: FallbackScopeRef, options?: {
772
+ exclude?: ServiceIdRef[];
773
+ unique?: boolean;
774
+ limit?: number;
775
+ }): ServiceIdRef[];
776
+ }
736
777
 
737
778
  type Env = "client" | "workspace";
738
779
  type VisibleGroup = {
@@ -816,30 +857,6 @@ declare class Selection {
816
857
  private findOptionById;
817
858
  }
818
859
 
819
- type EditorEvents = {
820
- "editor:command": {
821
- name: string;
822
- payload?: any;
823
- };
824
- "editor:change": {
825
- props: ServiceProps;
826
- reason: string;
827
- command?: string;
828
- };
829
- "editor:undo": {
830
- stackSize: number;
831
- index: number;
832
- };
833
- "editor:redo": {
834
- stackSize: number;
835
- index: number;
836
- };
837
- "editor:error": {
838
- message: string;
839
- code?: string;
840
- meta?: any;
841
- };
842
- };
843
860
  type Command = {
844
861
  name: string;
845
862
  do(): void;
@@ -856,19 +873,6 @@ type EditorOptions = {
856
873
  policiesRaw?: unknown;
857
874
  selectionProps?: SelectionOptions;
858
875
  };
859
- type ConnectKind = "bind" | "include" | "exclude";
860
-
861
- /** Exported alias so the schema generator can target an array */
862
- type AdminPolicies = DynamicRule[];
863
-
864
- type EventMap = Record<string, unknown>;
865
- declare class EventBus<E extends EventMap> {
866
- private listeners;
867
- on<K extends keyof E>(event: K, handler: (payload: E[K]) => void): () => void;
868
- once<K extends keyof E>(event: K, handler: (payload: E[K]) => void): () => void;
869
- emit<K extends keyof E>(event: K, payload: E[K]): void;
870
- clear(): void;
871
- }
872
876
 
873
877
  type PolicyDiagnostic = {
874
878
  ruleIndex: number;
@@ -1852,4 +1856,208 @@ declare function useOrderFlow(): UseOrderFlowReturn;
1852
1856
  */
1853
1857
  declare function registerEntries(registry: Registry): void;
1854
1858
 
1855
- export { type Adapter, type AdapterCtx, 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 FormProviderProps, type FormSnapshot, type GraphEdge, type GraphNode, type GraphSnapshot, type IdType, type InputDescriptor, type InputKind, type InputVariant, type InputWrapperProps, type LayoutState, type NodeIdRef, type NodeKind, type NodePos, type NodePositions, type NodeView, type NoticeKind, type NoticeSeverity, type NoticeTarget, type NoticeType, OrderFlowProvider, 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 ServicePropsNotice, 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, registerEntries, resolveInputDescriptor, useFormApi, useInputs, useOptionalFormApi, useOrderFlow, useOrderFlowContext };
1859
+ type Props$4 = {
1860
+ className?: string;
1861
+ fallbacks?: ServiceProps["fallbacks"];
1862
+ props?: ServiceProps;
1863
+ snapshot?: OrderSnapshot;
1864
+ primaryServices?: DgpServiceMap;
1865
+ eligibleServices?: DgpServiceMap;
1866
+ settings?: FallbackSettings;
1867
+ initialServiceId?: ServiceIdRef;
1868
+ onSettingsChange?: FallbackEditorProviderProps["onSettingsChange"];
1869
+ onSave?: FallbackEditorProviderProps["onSave"];
1870
+ onValidate?: FallbackEditorProviderProps["onValidate"];
1871
+ onReset?: FallbackEditorProviderProps["onReset"];
1872
+ };
1873
+ declare function FallbackEditor({ className, fallbacks, props, snapshot, primaryServices, eligibleServices, settings, initialServiceId, onSettingsChange, onSave, onValidate, onReset, }: Props$4): react_jsx_runtime.JSX.Element;
1874
+
1875
+ declare function useFallbackEditor(): {
1876
+ editor: FallbackEditor$1;
1877
+ version: number;
1878
+ serviceProps?: ServiceProps;
1879
+ snapshot?: OrderSnapshot;
1880
+ primaryServices: DgpServiceMap;
1881
+ eligibleServices: DgpServiceMap;
1882
+ activeServiceId?: ServiceIdRef;
1883
+ setActiveServiceId: React.Dispatch<React.SetStateAction<ServiceIdRef | undefined>>;
1884
+ activeTab: "registrations" | "settings";
1885
+ setActiveTab: React.Dispatch<React.SetStateAction<"registrations" | "settings">>;
1886
+ state: ReturnType<FallbackEditor$1["state"]>;
1887
+ value: ReturnType<FallbackEditor$1["value"]>;
1888
+ settings: FallbackSettings;
1889
+ settingsSaving: boolean;
1890
+ headerSaving: boolean;
1891
+ headerValidating: boolean;
1892
+ headerResetting: boolean;
1893
+ saveSettings: (next: FallbackSettings) => Promise<FallbackSettings>;
1894
+ saveFallbacks: () => Promise<ServiceProps["fallbacks"] | void>;
1895
+ validateFallbacks: () => Promise<void>;
1896
+ resetEditor: () => Promise<void>;
1897
+ reset: () => void;
1898
+ get: FallbackEditor$1["get"];
1899
+ getScope: FallbackEditor$1["getScope"];
1900
+ check: FallbackEditor$1["check"];
1901
+ eligible: FallbackEditor$1["eligible"];
1902
+ add: FallbackEditor$1["add"];
1903
+ addMany: FallbackEditor$1["addMany"];
1904
+ remove: FallbackEditor$1["remove"];
1905
+ replace: FallbackEditor$1["replace"];
1906
+ clear: FallbackEditor$1["clear"];
1907
+ };
1908
+ declare function useActiveFallbackRegistrations(): FallbackRegistration[];
1909
+ declare function usePrimaryServiceList(): {
1910
+ id: ServiceIdRef;
1911
+ name?: string;
1912
+ platform?: string;
1913
+ rate?: number;
1914
+ }[];
1915
+ declare function useEligibleServiceList(): {
1916
+ id: ServiceIdRef;
1917
+ name?: string;
1918
+ platform?: string;
1919
+ rate?: number;
1920
+ }[];
1921
+
1922
+ type Item = {
1923
+ id: ServiceIdRef;
1924
+ name?: string;
1925
+ platform?: string;
1926
+ rate?: number;
1927
+ };
1928
+ type Props$3 = {
1929
+ items: Item[];
1930
+ selected: Set<string>;
1931
+ onToggle: (id: ServiceIdRef) => void;
1932
+ height?: number;
1933
+ rowHeight?: number;
1934
+ emptyText?: string;
1935
+ };
1936
+ declare function VirtualServiceList({ items, selected, onToggle, height, rowHeight, emptyText, }: Props$3): react_jsx_runtime.JSX.Element;
1937
+
1938
+ type EditorSettings = {
1939
+ requireConstraintFit: boolean;
1940
+ ratePolicy: "lte_primary" | "ignore";
1941
+ selectionStrategy: "priority" | "cheapest";
1942
+ mode: "strict" | "dev";
1943
+ };
1944
+ type ServiceSummary = {
1945
+ id: ServiceIdRef;
1946
+ name: string;
1947
+ platform?: string;
1948
+ rate?: number;
1949
+ flags?: Record<string, boolean>;
1950
+ description?: string;
1951
+ };
1952
+ type RegistrationScope = "global" | "node";
1953
+ type RegistrationItem = {
1954
+ primary: ServiceIdRef;
1955
+ scope: RegistrationScope;
1956
+ scopeId?: string;
1957
+ nodeKind?: "tag" | "field" | "option";
1958
+ nodeLabel?: string;
1959
+ services: ServiceIdRef[];
1960
+ };
1961
+ type ValidationTone = "ok" | "warn" | "error";
1962
+ type ValidationMessage = {
1963
+ primary: ServiceIdRef;
1964
+ scope: RegistrationScope;
1965
+ scopeId?: string;
1966
+ candidate?: ServiceIdRef;
1967
+ tone: ValidationTone;
1968
+ title: string;
1969
+ message: string;
1970
+ };
1971
+ type FallbackEditorData = {
1972
+ services: ServiceSummary[];
1973
+ registrations: RegistrationItem[];
1974
+ diagnostics: ValidationMessage[];
1975
+ settings: EditorSettings;
1976
+ };
1977
+
1978
+ declare function FallbackDetailsPanel(): react_jsx_runtime.JSX.Element;
1979
+
1980
+ type Props$2 = {
1981
+ onReset?: () => void | Promise<void>;
1982
+ onValidate?: () => void | Promise<void>;
1983
+ onSave?: () => void | Promise<void>;
1984
+ resetting?: boolean;
1985
+ validating?: boolean;
1986
+ saving?: boolean;
1987
+ };
1988
+ declare function FallbackEditorHeader({ onReset, onValidate, onSave, resetting, validating, saving, }: Props$2): react_jsx_runtime.JSX.Element;
1989
+
1990
+ declare function FallbackSettingsPanel(): react_jsx_runtime.JSX.Element;
1991
+
1992
+ type TabKey = "registrations" | "settings";
1993
+ type FallbackEditorProviderProps = {
1994
+ children: React.ReactNode;
1995
+ fallbacks?: ServiceProps["fallbacks"];
1996
+ props?: ServiceProps;
1997
+ snapshot?: OrderSnapshot;
1998
+ primaryServices?: DgpServiceMap;
1999
+ eligibleServices?: DgpServiceMap;
2000
+ settings?: FallbackSettings;
2001
+ initialServiceId?: ServiceIdRef;
2002
+ initialTab?: TabKey;
2003
+ onSettingsChange?: (next: FallbackSettings) => Promise<FallbackSettings> | FallbackSettings;
2004
+ onSave?: (next: ServiceProps["fallbacks"]) => Promise<ServiceProps["fallbacks"] | void> | ServiceProps["fallbacks"] | void;
2005
+ onValidate?: (next: ServiceProps["fallbacks"]) => Promise<void> | void;
2006
+ onReset?: () => Promise<void> | void;
2007
+ };
2008
+ type FallbackEditorContextValue = {
2009
+ editor: FallbackEditor$1;
2010
+ version: number;
2011
+ serviceProps?: ServiceProps;
2012
+ snapshot?: OrderSnapshot;
2013
+ primaryServices: DgpServiceMap;
2014
+ eligibleServices: DgpServiceMap;
2015
+ activeServiceId?: ServiceIdRef;
2016
+ setActiveServiceId: React.Dispatch<React.SetStateAction<ServiceIdRef | undefined>>;
2017
+ activeTab: TabKey;
2018
+ setActiveTab: React.Dispatch<React.SetStateAction<TabKey>>;
2019
+ state: ReturnType<FallbackEditor$1["state"]>;
2020
+ value: ReturnType<FallbackEditor$1["value"]>;
2021
+ settings: FallbackSettings;
2022
+ settingsSaving: boolean;
2023
+ headerSaving: boolean;
2024
+ headerValidating: boolean;
2025
+ headerResetting: boolean;
2026
+ saveSettings: (next: FallbackSettings) => Promise<FallbackSettings>;
2027
+ saveFallbacks: () => Promise<ServiceProps["fallbacks"] | void>;
2028
+ validateFallbacks: () => Promise<void>;
2029
+ resetEditor: () => Promise<void>;
2030
+ reset: () => void;
2031
+ get: FallbackEditor$1["get"];
2032
+ getScope: FallbackEditor$1["getScope"];
2033
+ check: FallbackEditor$1["check"];
2034
+ eligible: FallbackEditor$1["eligible"];
2035
+ add: FallbackEditor$1["add"];
2036
+ addMany: FallbackEditor$1["addMany"];
2037
+ remove: FallbackEditor$1["remove"];
2038
+ replace: FallbackEditor$1["replace"];
2039
+ clear: FallbackEditor$1["clear"];
2040
+ };
2041
+ declare function FallbackEditorProvider({ children, fallbacks, props, snapshot, primaryServices, eligibleServices, settings: initialSettings, initialServiceId, initialTab, onSettingsChange, onSave, onValidate, onReset, }: FallbackEditorProviderProps): react_jsx_runtime.JSX.Element;
2042
+ declare function useFallbackEditorContext(): FallbackEditorContextValue;
2043
+
2044
+ declare function FallbackServiceSidebar(): react_jsx_runtime.JSX.Element;
2045
+
2046
+ declare function FallbackRegistrationsPanel(): react_jsx_runtime.JSX.Element;
2047
+
2048
+ type Props$1 = {
2049
+ open: boolean;
2050
+ onClose: () => void;
2051
+ context: FallbackScopeRef | null;
2052
+ primaryId?: ServiceIdRef;
2053
+ };
2054
+ declare function FallbackAddCandidatesDialog({ open, onClose, context, primaryId, }: Props$1): react_jsx_runtime.JSX.Element | null;
2055
+
2056
+ type Props = {
2057
+ open: boolean;
2058
+ onClose: () => void;
2059
+ onSelect: (context: FallbackScopeRef, primaryId: ServiceIdRef) => void;
2060
+ };
2061
+ declare function FallbackAddRegistrationDialog({ open, onClose, onSelect, }: Props): react_jsx_runtime.JSX.Element | null;
2062
+
2063
+ export { type Adapter, type AdapterCtx, CanvasAPI, type EditorSettings, EventBus, type EventMap, FallbackAddCandidatesDialog, FallbackAddRegistrationDialog, FallbackDetailsPanel, FallbackEditor, type FallbackEditorData, FallbackEditorHeader, FallbackEditorProvider, type FallbackEditorProviderProps, FallbackRegistrationsPanel, FallbackServiceSidebar, FallbackSettingsPanel, type FormApi, FormProvider, type FormProviderProps, type FormSnapshot, type InputDescriptor, type InputKind, type InputVariant, type InputWrapperProps, OrderFlowProvider, Provider, type RegistrationItem, type RegistrationScope, type Registry, type ServiceSummary, type ValidationMessage, type ValidationTone, VirtualServiceList, Wrapper, createInputRegistry, registerEntries, resolveInputDescriptor, useActiveFallbackRegistrations, useEligibleServiceList, useFallbackEditor, useFallbackEditorContext, useFormApi, useInputs, useOptionalFormApi, useOrderFlow, useOrderFlowContext, usePrimaryServiceList };