@dreamboard-games/sdk 0.5.0-alpha.3 → 0.5.0-alpha.4

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.
Files changed (36) hide show
  1. package/README.md +52 -2
  2. package/dist/board-target-B0QG6OYx.d.ts +8 -0
  3. package/dist/chunk-3JXUL4VN.js +139 -0
  4. package/dist/chunk-3JXUL4VN.js.map +1 -0
  5. package/dist/{chunk-LRILEVKC.js → chunk-C77SYIVT.js} +82 -109
  6. package/dist/chunk-C77SYIVT.js.map +1 -0
  7. package/dist/{chunk-SP234IY4.js → chunk-OOFMBCFF.js} +178 -203
  8. package/dist/chunk-OOFMBCFF.js.map +1 -0
  9. package/dist/{chunk-ZPRKWPEP.js → chunk-UG2Q4NLG.js} +108 -29
  10. package/dist/chunk-UG2Q4NLG.js.map +1 -0
  11. package/dist/{chunk-FMM5LVJW.js → chunk-XU6WJW6K.js} +26 -12
  12. package/dist/chunk-XU6WJW6K.js.map +1 -0
  13. package/dist/{diagnostics-B0lgnuZ6.d.ts → diagnostics-B8d5aLad.d.ts} +2 -2
  14. package/dist/{definition-BaHNPZol.d.ts → extract-gVKcB8Og.d.ts} +484 -387
  15. package/dist/index.d.ts +404 -175
  16. package/dist/index.js +266 -215
  17. package/dist/index.js.map +1 -1
  18. package/dist/model-vdpLHrxh.d.ts +432 -0
  19. package/dist/react.d.ts +35 -7
  20. package/dist/react.js +136 -8
  21. package/dist/react.js.map +1 -1
  22. package/dist/reducer.d.ts +169 -118
  23. package/dist/reducer.js +206 -328
  24. package/dist/reducer.js.map +1 -1
  25. package/dist/testing.d.ts +5 -3
  26. package/dist/testing.js +9 -6
  27. package/dist/testing.js.map +1 -1
  28. package/dist/{types-DimqnJBq.d.ts → types-Bj5ZLTUV.d.ts} +38 -10
  29. package/package.json +19 -16
  30. package/dist/chunk-FMM5LVJW.js.map +0 -1
  31. package/dist/chunk-LRILEVKC.js.map +0 -1
  32. package/dist/chunk-SP234IY4.js.map +0 -1
  33. package/dist/chunk-VUWNJXLJ.js +0 -52
  34. package/dist/chunk-VUWNJXLJ.js.map +0 -1
  35. package/dist/chunk-ZPRKWPEP.js.map +0 -1
  36. package/dist/model-BHro6yXu.d.ts +0 -288
@@ -2,8 +2,12 @@ import {
2
2
  immutableCopy
3
3
  } from "./chunk-DMFVAZI2.js";
4
4
  import {
5
- inputValueInDomain
6
- } from "./chunk-VUWNJXLJ.js";
5
+ inputDomainErrors,
6
+ inputTargetInDomain,
7
+ inputValueInDomain,
8
+ inputValueKey,
9
+ requireLookup
10
+ } from "./chunk-3JXUL4VN.js";
7
11
 
8
12
  // src/headless/instance.ts
9
13
  import { createStore } from "@tanstack/store";
@@ -30,68 +34,13 @@ function validateInteractionInputDomains(descriptor, params) {
30
34
  for (const rawInput of descriptor.inputs) {
31
35
  const input = rawInput;
32
36
  const value = params[input.key];
33
- if (value === void 0 || value === null) continue;
34
- const selectionErrors = validateInputSelection(input, value);
35
- for (const error of selectionErrors) {
37
+ if (value === void 0) continue;
38
+ for (const error of inputDomainErrors(
39
+ input.domain,
40
+ value,
41
+ input.domain.selection
42
+ ))
36
43
  pushFieldError(fieldErrors, input.key, error);
37
- }
38
- if (input.domain.type === "choiceList") {
39
- if (!Array.isArray(value)) {
40
- pushFieldError(fieldErrors, input.key, "Expected a list of choices.");
41
- continue;
42
- }
43
- const min = input.domain.min ?? 0;
44
- const max = input.domain.max ?? input.domain.choices?.length ?? Number.POSITIVE_INFINITY;
45
- if (value.length < min) {
46
- pushFieldError(
47
- fieldErrors,
48
- input.key,
49
- `Choose at least ${min} ${pluralize("option", min)}.`
50
- );
51
- }
52
- if (value.length > max) {
53
- pushFieldError(
54
- fieldErrors,
55
- input.key,
56
- `Choose at most ${max} ${pluralize("option", max)}.`
57
- );
58
- }
59
- const allowed = new Set(
60
- input.domain.choices?.map((choice) => choice.value)
61
- );
62
- if (allowed.size > 0 && value.some((item) => !allowed.has(String(item)))) {
63
- pushFieldError(
64
- fieldErrors,
65
- input.key,
66
- "Selected choice is not eligible."
67
- );
68
- }
69
- }
70
- if (input.domain.type === "choice") {
71
- const allowed = new Set(
72
- input.domain.choices?.map((choice) => choice.value)
73
- );
74
- if (allowed.size > 0 && !allowed.has(value)) {
75
- pushFieldError(
76
- fieldErrors,
77
- input.key,
78
- "Selected choice is not eligible."
79
- );
80
- }
81
- }
82
- if (isResolvedTargetDomain(input.domain)) {
83
- const values = valuesForSelection(input.domain.selection, value);
84
- for (const item of values) {
85
- if (!input.domain.eligibleTargets.includes(String(item))) {
86
- pushFieldError(
87
- fieldErrors,
88
- input.key,
89
- "Selected target is not eligible."
90
- );
91
- break;
92
- }
93
- }
94
- }
95
44
  }
96
45
  return fieldErrors;
97
46
  }
@@ -110,10 +59,10 @@ function isManyInput(input) {
110
59
  }
111
60
  function toggleManyValue(current, value, selection) {
112
61
  if (selection.mode !== "many") return [value];
113
- const previous = Array.isArray(current) ? current.map((item) => String(item)) : [];
114
- const existing = previous.indexOf(value);
62
+ const previous = Array.isArray(current) ? current : [];
63
+ const existing = previous.findIndex((item) => sameValue(item, value));
115
64
  if (existing >= 0) {
116
- return previous.filter((item) => item !== value);
65
+ return previous.filter((item) => !sameValue(item, value));
117
66
  }
118
67
  if (selection.max !== void 0 && previous.length >= selection.max) {
119
68
  return previous;
@@ -123,9 +72,8 @@ function toggleManyValue(current, value, selection) {
123
72
  function isManyTargetSelectable(input, current, targetId) {
124
73
  const selection = inputSelection(input);
125
74
  if (selection?.mode !== "many") return true;
126
- const currentValues = Array.isArray(current) ? current.map((item) => String(item)) : [];
127
- if (currentValues.includes(targetId)) return true;
128
- if (selection.distinct && currentValues.includes(targetId)) return false;
75
+ const currentValues = Array.isArray(current) ? current : [];
76
+ if (currentValues.some((item) => sameValue(item, targetId))) return true;
129
77
  return selection.max === void 0 || currentValues.length < selection.max;
130
78
  }
131
79
  function hasInteractionFieldErrors(fieldErrors) {
@@ -136,52 +84,21 @@ function hasInteractionFieldErrors(fieldErrors) {
136
84
  function inputByKey(descriptor, key) {
137
85
  return descriptor.inputs.find((input) => input.key === key);
138
86
  }
139
- function isTargetDomain(domain) {
140
- if (!domain) return false;
141
- return domain.type === "cardTarget" || domain.type === "boardTarget";
142
- }
143
- function isResolvedTargetDomain(domain) {
144
- return (domain.type === "cardTarget" || domain.type === "boardTarget") && domain.projection === "resolved";
145
- }
146
- function validateInputSelection(input, value) {
147
- const selection = inputSelection(input);
148
- if (selection?.mode !== "many") return [];
149
- if (!Array.isArray(value)) return ["Expected a list of values."];
150
- const errors = [];
151
- const min = selection.min;
152
- if (value.length < min) {
153
- errors.push(`Choose at least ${min} ${pluralize("value", min)}.`);
154
- }
155
- if (selection.max !== void 0 && value.length > selection.max) {
156
- errors.push(
157
- `Choose at most ${selection.max} ${pluralize("value", selection.max)}.`
158
- );
159
- }
160
- if (selection.distinct) {
161
- const seen = /* @__PURE__ */ new Set();
162
- for (const item of value) {
163
- const key = String(item);
164
- if (seen.has(key)) {
165
- errors.push("Choose each value only once.");
166
- break;
167
- }
168
- seen.add(key);
169
- }
170
- }
171
- return errors;
172
- }
173
- function valuesForSelection(selection, value) {
174
- if (selection?.mode === "many") return Array.isArray(value) ? value : [];
175
- return [value];
176
- }
177
87
  function inputSelection(input) {
178
- return "selection" in input.domain ? input.domain.selection : void 0;
88
+ if (input.domain.selection) return input.domain.selection;
89
+ if (input.domain.type === "choiceList")
90
+ return {
91
+ mode: "many",
92
+ min: input.domain.min ?? 0,
93
+ max: input.domain.max ?? input.domain.choices.length
94
+ };
95
+ return void 0;
179
96
  }
180
97
  function pushFieldError(fieldErrors, key, message) {
181
98
  fieldErrors[key] = [...fieldErrors[key] ?? [], message];
182
99
  }
183
- function pluralize(word, count) {
184
- return count === 1 ? word : `${word}s`;
100
+ function sameValue(left, right) {
101
+ return inputValueKey(left) === inputValueKey(right);
185
102
  }
186
103
 
187
104
  // src/headless/interaction-router.ts
@@ -236,8 +153,12 @@ function getInteractionDraftReadiness(descriptor, draft) {
236
153
  function routeInteractionTarget(store, descriptor, target) {
237
154
  const input = inputByKey(descriptor, target.inputKey);
238
155
  const currentDraft = store.getDraft(descriptor.interactionKey);
239
- const selection = isTargetDomain(input?.domain) ? input.domain.selection : void 0;
240
- const targetValue = selection?.mode === "many" ? toggleManyValue(currentDraft[target.inputKey], target.value, selection) : target.value;
156
+ const selection = input ? inputSelection(input) : void 0;
157
+ const targetValue = selection?.mode === "many" ? toggleManyValue(
158
+ currentDraft[target.inputKey] ?? input?.defaultValue,
159
+ target.value,
160
+ selection
161
+ ) : target.value;
241
162
  const params = applyInteractionDraftMutation(store, descriptor, [
242
163
  ...Object.entries(target.extraInputs ?? {}).map(([key, value]) => ({
243
164
  key,
@@ -258,18 +179,22 @@ function routeCardInputIntent(store, descriptor, intent) {
258
179
  });
259
180
  }
260
181
  function shouldAutoSubmitInteraction(descriptor) {
261
- return descriptor.commit.mode === "autoWhenReady" && !descriptor.inputs.some((input) => isManyInput(input));
182
+ return descriptor.commit.mode === "autoWhenReady" && !descriptor.inputs.some((input) => input.domain.selection?.mode === "many");
262
183
  }
263
184
 
264
185
  // src/headless/instance.ts
265
186
  var EMPTY = Object.freeze({});
266
187
  var EMPTY_DRAFTS = Object.freeze({});
267
- var equalValue = (a, b) => Object.is(a, b) || typeof a === "object" && typeof b === "object" && JSON.stringify(a) === JSON.stringify(b);
188
+ var equalValue = (a, b) => Object.is(a, b) || typeof a === "object" && typeof b === "object" && inputValueKey(a) === inputValueKey(b);
268
189
  function inDomain(input, value, partial = false) {
269
190
  return inputValueInDomain(input.domain, value, input.domain.selection, {
270
191
  ignoreMinimum: partial
271
192
  });
272
193
  }
194
+ function matchesBoardTarget(input, target) {
195
+ const domain = input.domain;
196
+ return domain.type === "boardTarget" && domain.valueKind === target.valueKind && (domain.targetKind === target.kind || target.kind === "space" && domain.targetKind === "tile") && domain.boardId === (target.valueKind === "board-id" ? target.boardId : target.value.boardId);
197
+ }
273
198
  function immutableValues(value) {
274
199
  return immutableCopy(value);
275
200
  }
@@ -286,7 +211,7 @@ function data(action, interaction, input, value) {
286
211
  var AmbiguousTargetError = class extends Error {
287
212
  constructor(id) {
288
213
  super(
289
- `Target '${id}' belongs to more than one interaction. Choose an interaction explicitly.`
214
+ `Target '${id}' belongs to more than one eligible input. Choose an interaction and input explicitly.`
290
215
  );
291
216
  this.name = "AmbiguousTargetError";
292
217
  }
@@ -351,19 +276,20 @@ var InteractionObject = class {
351
276
  return this.descriptor.step?.index ?? null;
352
277
  }
353
278
  getInput(key) {
279
+ return requireLookup(
280
+ this.findInput(key),
281
+ `Input in interaction ${this.key}`,
282
+ key
283
+ );
284
+ }
285
+ findInput(key) {
354
286
  return this.inputObjects.find((input) => input.key === key);
355
287
  }
356
288
  getInputs() {
357
289
  return this.inputObjects;
358
290
  }
359
291
  readiness() {
360
- const readiness = getInteractionDraftReadiness(this.descriptor, this.draft);
361
- return {
362
- ...readiness,
363
- ready: readiness.ready && this.descriptor.inputs.every(
364
- (input) => inDomain(input, readiness.values[input.key])
365
- )
366
- };
292
+ return getInteractionDraftReadiness(this.descriptor, this.draft);
367
293
  }
368
294
  getIsReady() {
369
295
  return this.readiness().ready;
@@ -392,7 +318,7 @@ var InteractionObject = class {
392
318
  getSubmitHandler() {
393
319
  return (event) => {
394
320
  event?.preventDefault();
395
- this.owner.handle(this.submit());
321
+ this.owner.handle(() => this.submit());
396
322
  };
397
323
  }
398
324
  getSubmitProps() {
@@ -443,7 +369,7 @@ var InputObject = class {
443
369
  };
444
370
  return getInteractionDraftReadiness(descriptor, {
445
371
  [this.key]: this.getValue()
446
- }).ready && inDomain(this.descriptor, this.getValue());
372
+ }).ready;
447
373
  }
448
374
  getEligibleTargets() {
449
375
  const domain = this.descriptor.domain;
@@ -455,11 +381,7 @@ var InputObject = class {
455
381
  }
456
382
  getIsEligible(value) {
457
383
  const input = this.descriptor;
458
- const selection = input.domain.selection;
459
- return inDomain(
460
- { ...input, domain: { ...input.domain, selection: void 0 } },
461
- value
462
- ) && (!selection || selection.mode !== "many" || isManyTargetSelectable(input, this.getValue(), String(value)));
384
+ return inputTargetInDomain(input.domain, value) && isManyTargetSelectable(input, this.getValue(), value);
463
385
  }
464
386
  getIsSelected(value) {
465
387
  const current = this.getValue();
@@ -544,16 +466,16 @@ var CardObject = class {
544
466
  )
545
467
  );
546
468
  }
547
- getCanSelect() {
469
+ getCanSelect(options) {
548
470
  return this.routes.some(
549
- (route) => route.getIsAvailable() && route.status === "open" && route.connected && route.getInputs().some(
550
- (input) => input.descriptor.domain.type === "cardTarget" && input.getIsEligible(this.id)
471
+ (route) => route.getIsAvailable() && (!options?.interaction || route.key === options.interaction) && route.status === "open" && route.connected && route.getInputs().some(
472
+ (input) => input.descriptor.domain.type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(this.id)
551
473
  )
552
474
  );
553
475
  }
554
476
  select(options) {
555
477
  if (this.epoch !== this.owner.epoch || this.owner.disposed) return;
556
- this.owner.selectCard(this.id, options?.interaction);
478
+ this.owner.selectCard(this.id, options);
557
479
  }
558
480
  getSelectHandler(options) {
559
481
  return () => this.select(options);
@@ -561,13 +483,14 @@ var CardObject = class {
561
483
  getProps(options) {
562
484
  return {
563
485
  type: "button",
564
- disabled: !this.getCanSelect(),
486
+ disabled: !this.getCanSelect(options),
565
487
  "data-action": "select",
566
488
  "data-value": this.id,
567
489
  "data-interaction": options?.interaction,
490
+ "data-input": options?.input,
568
491
  "data-eligible": this.getIsEligible(),
569
492
  "data-selected": this.getIsSelected(),
570
- "data-disabled": !this.getCanSelect(),
493
+ "data-disabled": !this.getCanSelect(options),
571
494
  "data-hidden": this.hidden,
572
495
  "data-seat": this.seat,
573
496
  onClick: this.getSelectHandler(options)
@@ -593,6 +516,9 @@ var ZoneObject = class {
593
516
  return this.count === 0;
594
517
  }
595
518
  getCard(id) {
519
+ return requireLookup(this.findCard(id), `Card in zone ${this.id}`, id);
520
+ }
521
+ findCard(id) {
596
522
  return this.cards.find((card) => card.id === id);
597
523
  }
598
524
  getCards(options) {
@@ -649,6 +575,7 @@ var Controller = class {
649
575
  };
650
576
  subscription;
651
577
  sourceState;
578
+ reportedFailures = /* @__PURE__ */ new WeakMap();
652
579
  pending = null;
653
580
  building;
654
581
  rootProperties = /* @__PURE__ */ new Map();
@@ -693,6 +620,7 @@ var Controller = class {
693
620
  "view",
694
621
  "version",
695
622
  "connection",
623
+ "failure",
696
624
  "request",
697
625
  "state",
698
626
  "phase",
@@ -729,7 +657,7 @@ var Controller = class {
729
657
  data: immutableCopy(data2),
730
658
  game: this.instance
731
659
  }),
732
- routeTarget: (kind, id, options2) => this.routeTarget(kind, id, options2?.interaction, options2?.boardId),
660
+ routeTarget: (target, options2) => this.routeTarget(target, options2),
733
661
  routeCardDrop: (cardId, target) => this.routeCardDrop(cardId, target),
734
662
  invalidate: () => {
735
663
  if (!this.disposed) this.refresh();
@@ -762,6 +690,7 @@ var Controller = class {
762
690
  }
763
691
  this.refresh();
764
692
  this.subscribeSource();
693
+ this.reportSourceFailure();
765
694
  }
766
695
  install(target, extension) {
767
696
  if (!extension) return;
@@ -843,7 +772,7 @@ var Controller = class {
843
772
  return base;
844
773
  }
845
774
  current(key) {
846
- return this.store.get().interactions.get(key);
775
+ return this.store.get().interactions.find(key);
847
776
  }
848
777
  editable(key) {
849
778
  const interaction = this.current(key);
@@ -851,11 +780,11 @@ var Controller = class {
851
780
  }
852
781
  setInput(key, inputKey, value) {
853
782
  const interaction = this.editable(key);
854
- if (!interaction?.getInput(inputKey)) return;
783
+ if (!interaction?.findInput(inputKey)) return;
855
784
  this.writeDraft(key, { ...this.drafts()[key], [inputKey]: value });
856
785
  }
857
786
  clearInput(key, inputKey) {
858
- if (!this.editable(key)?.getInput(inputKey)) return;
787
+ if (!this.editable(key)?.findInput(inputKey)) return;
859
788
  const value = { ...this.drafts()[key] };
860
789
  delete value[inputKey];
861
790
  this.writeDraft(key, value);
@@ -869,9 +798,9 @@ var Controller = class {
869
798
  const interaction = route ?? this.editable(key);
870
799
  if (route && (!route.currentLifetime() || !route.getIsAvailable() || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending))
871
800
  return;
872
- const input = interaction?.getInput(inputKey);
801
+ const input = interaction?.findInput(inputKey);
873
802
  if (!interaction || !input?.getIsEligible(value)) return;
874
- if (typeof value === "string" && (input.descriptor.domain.type === "cardTarget" || input.descriptor.domain.type === "boardTarget")) {
803
+ if (isManyInput(input.descriptor) || typeof value === "string" && (input.descriptor.domain.type === "cardTarget" || input.descriptor.domain.type === "boardTarget")) {
875
804
  let next = { ...this.drafts()[key] };
876
805
  routeInteractionTarget(
877
806
  {
@@ -891,33 +820,45 @@ var Controller = class {
891
820
  this.activate(key);
892
821
  const current = this.current(key);
893
822
  if (current && Object.hasOwn(this.drafts()[key] ?? EMPTY, inputKey) && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
894
- this.handle(this.submit(key, false));
895
- }
896
- selectCard(id, explicit) {
897
- const card = this.store.get().cards.get(id);
898
- if (!card) return;
899
- let routes = card.routes.filter(
900
- (route2) => route2.getIsAvailable() && route2.currentLifetime() && !this.sourceState.request && this.sourceState.connection === "ready" && route2.getInputs().some(
901
- (input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(id)
902
- )
903
- );
904
- if (explicit) routes = routes.filter((route2) => route2.key === explicit);
905
- if (routes.length > 1) throw new AmbiguousTargetError(id);
906
- const route = routes[0];
907
- const input = route?.getInputs().find(
908
- (input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(id)
909
- );
910
- if (route && input) this.select(route.key, input.key, id, route);
823
+ this.handle(() => this.submit(key, false));
824
+ }
825
+ selectCard(id, options) {
826
+ const card = this.store.get().cards.find(id);
827
+ if (!card || this.sourceState.request || this.sourceState.connection !== "ready")
828
+ return;
829
+ const candidates = card.routes.flatMap((interaction) => {
830
+ if (!interaction.getIsAvailable() || !interaction.currentLifetime() || options?.interaction && interaction.key !== options.interaction)
831
+ return [];
832
+ return interaction.getInputs().filter(
833
+ (input) => input.descriptor.domain.type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(id)
834
+ ).map((input) => ({ interaction, input }));
835
+ });
836
+ if (candidates.length > 1) throw new AmbiguousTargetError(id);
837
+ const candidate = candidates[0];
838
+ if (candidate)
839
+ this.select(
840
+ candidate.interaction.key,
841
+ candidate.input.key,
842
+ id,
843
+ candidate.interaction
844
+ );
911
845
  }
912
- handle(promise) {
913
- void promise.then(
846
+ handle(submit) {
847
+ const source = this.options.source;
848
+ const active = () => !this.disposed && this.options.source === source;
849
+ void submit().then(
914
850
  (result) => {
915
- if (!result.accepted)
851
+ if (active() && !result.accepted)
916
852
  this.options.onError?.(
917
853
  new Error(result.message ?? result.errorCode, { cause: result })
918
854
  );
919
855
  },
920
- (error) => this.options.onError?.(error)
856
+ (error) => {
857
+ if (!active()) return;
858
+ const state = source.store.get();
859
+ if (state.failure === error || state.connection === "closed") return;
860
+ this.options.onError?.(error);
861
+ }
921
862
  );
922
863
  }
923
864
  async submit(key, cancel) {
@@ -968,7 +909,7 @@ var Controller = class {
968
909
  const operation = this.pending;
969
910
  if (!operation?.accepted || this.sourceState.request || operation.source !== this.options.source || operation.epoch !== this.epoch)
970
911
  return;
971
- if (this.sourceState.connection === "closed") {
912
+ if (this.sourceState.connection === "closed" || this.sourceState.connection === "failed") {
972
913
  this.pending = null;
973
914
  this.refresh();
974
915
  return;
@@ -980,6 +921,13 @@ var Controller = class {
980
921
  this.reset(operation.key);
981
922
  else this.refresh();
982
923
  }
924
+ reportSourceFailure() {
925
+ const failure = this.sourceState.failure;
926
+ if (!failure || !this.options.onError || this.reportedFailures.get(this.options.source) === failure)
927
+ return;
928
+ this.reportedFailures.set(this.options.source, failure);
929
+ this.options.onError?.(failure);
930
+ }
983
931
  subscribeSource() {
984
932
  const source = this.options.source;
985
933
  this.subscription = source.store.subscribe(() => {
@@ -999,6 +947,7 @@ var Controller = class {
999
947
  this.reconcile();
1000
948
  this.refresh();
1001
949
  this.finishBarrier();
950
+ this.reportSourceFailure();
1002
951
  });
1003
952
  }
1004
953
  setOptions(next) {
@@ -1022,6 +971,7 @@ var Controller = class {
1022
971
  }
1023
972
  this.reconcile();
1024
973
  this.refresh();
974
+ this.reportSourceFailure();
1025
975
  }
1026
976
  reconcile() {
1027
977
  const snapshot = this.sourceState.snapshot;
@@ -1047,11 +997,12 @@ var Controller = class {
1047
997
  for (const [name, value] of Object.entries(values)) {
1048
998
  const input = descriptor.inputs.find((input2) => input2.key === name);
1049
999
  if (!input && this.pending?.key === key) continue;
1050
- if (input?.domain.selection?.mode === "many" && Array.isArray(value)) {
1000
+ const selection = input && inputSelection(input);
1001
+ if (input && selection?.mode === "many" && Array.isArray(value)) {
1051
1002
  const eligible = value.filter(
1052
- (item) => inputValueInDomain(input.domain, item)
1003
+ (item) => inputTargetInDomain(input.domain, item)
1053
1004
  );
1054
- const max = input.domain.selection.max;
1005
+ const max = selection.max;
1055
1006
  const retained = max === void 0 ? eligible : eligible.slice(0, max);
1056
1007
  if (retained.length !== value.length) {
1057
1008
  kept[name] = retained;
@@ -1082,11 +1033,7 @@ var Controller = class {
1082
1033
  this.subscription?.unsubscribe();
1083
1034
  this.pending = null;
1084
1035
  this.options.source.dispose();
1085
- this.sourceState = {
1086
- ...this.sourceState,
1087
- connection: "closed",
1088
- request: null
1089
- };
1036
+ this.sourceState = this.options.source.store.get();
1090
1037
  this.refresh();
1091
1038
  }
1092
1039
  assertCoverage() {
@@ -1096,40 +1043,35 @@ var Controller = class {
1096
1043
  if (missing.length)
1097
1044
  throw new Error(`Interactions not read: ${missing.join(", ")}`);
1098
1045
  }
1099
- routeTarget(kind, id, explicit, boardId) {
1100
- if (kind === "card") {
1101
- this.selectCard(id, explicit);
1046
+ routeTarget(target, options) {
1047
+ if (target.kind === "card") {
1048
+ this.selectCard(target.value, options);
1102
1049
  return;
1103
1050
  }
1104
1051
  const candidates = this.lastInteractions.flatMap(
1105
1052
  (interaction) => interaction.getInputs().filter(
1106
- (input) => input.descriptor.domain.type === "boardTarget" && (input.descriptor.domain.targetKind === kind || kind === "space" && input.descriptor.domain.targetKind === "tile") && (!boardId || input.descriptor.domain.boardId === boardId) && input.getIsEligible(id) && interaction.getIsAvailable() && (!explicit || interaction.key === explicit)
1053
+ (input) => matchesBoardTarget(input.descriptor, target) && input.getIsEligible(target.value) && interaction.getIsAvailable() && (!options?.interaction || interaction.key === options.interaction) && (!options?.input || input.key === options.input)
1107
1054
  ).map((input) => ({ interaction, input }))
1108
1055
  );
1109
- if (candidates.length > 1) throw new AmbiguousTargetError(id);
1056
+ if (candidates.length > 1)
1057
+ throw new AmbiguousTargetError(JSON.stringify(target.value));
1110
1058
  const match = candidates[0];
1111
- if (match) this.select(match.interaction.key, match.input.key, id);
1059
+ if (match)
1060
+ this.select(match.interaction.key, match.input.key, target.value);
1112
1061
  }
1113
1062
  routeCardDrop(cardId, target) {
1114
1063
  if (this.disposed || this.sourceState.request || this.pending || this.sourceState.connection !== "ready")
1115
1064
  return;
1116
- const card = this.store.get().cards.get(cardId);
1117
- if (!card) return;
1118
- const candidates = card.routes.flatMap((interaction2) => {
1119
- if (!interaction2.getIsAvailable() || target.interaction && interaction2.key !== target.interaction)
1120
- return [];
1121
- const cardInput2 = interaction2.getInputs().find(
1122
- (input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(cardId)
1123
- );
1124
- if (!cardInput2) return [];
1125
- return interaction2.getInputs().filter(
1126
- (input2) => input2.descriptor.domain.type === "boardTarget" && (input2.descriptor.domain.targetKind === target.kind || target.kind === "space" && input2.descriptor.domain.targetKind === "tile") && (!target.boardId || input2.descriptor.domain.boardId === target.boardId) && input2.getIsEligible(target.id)
1127
- ).map((input2) => ({ interaction: interaction2, cardInput: cardInput2, input: input2 }));
1128
- });
1129
- if (candidates.length > 1) throw new AmbiguousTargetError(target.id);
1130
- const candidate = candidates[0];
1131
- if (!candidate) return;
1132
- const { interaction, cardInput, input } = candidate;
1065
+ const card = this.store.get().cards.find(cardId);
1066
+ const interaction = card?.routes.find(
1067
+ (route) => route.key === target.interactionKey
1068
+ );
1069
+ if (!interaction?.currentLifetime() || !interaction.getIsAvailable())
1070
+ return;
1071
+ const cardInput = interaction.findInput(target.cardInputKey);
1072
+ const input = interaction.findInput(target.inputKey);
1073
+ if (!cardInput || cardInput.descriptor.domain.type !== "cardTarget" || !cardInput.getIsEligible(cardId) || !input || !matchesBoardTarget(input.descriptor, target) || !input.getIsEligible(target.value))
1074
+ return;
1133
1075
  let next = { ...this.drafts()[interaction.key] };
1134
1076
  routeCardInputIntent(
1135
1077
  {
@@ -1145,14 +1087,14 @@ var Controller = class {
1145
1087
  {
1146
1088
  cardId,
1147
1089
  cardInputKey: cardInput.key,
1148
- dropTarget: { inputKey: input.key, value: target.id }
1090
+ dropTarget: { inputKey: input.key, value: target.value }
1149
1091
  }
1150
1092
  );
1151
1093
  this.writeDraft(interaction.key, next);
1152
1094
  this.activate(interaction.key);
1153
1095
  const current = this.current(interaction.key);
1154
1096
  if (current && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
1155
- this.handle(current.submit());
1097
+ this.handle(() => current.submit());
1156
1098
  }
1157
1099
  warnUnread() {
1158
1100
  const environment = import.meta.env?.DEV ?? globalThis.process?.env?.NODE_ENV === "development";
@@ -1170,7 +1112,7 @@ var Controller = class {
1170
1112
  }
1171
1113
  build(previous) {
1172
1114
  this.trackDrafts();
1173
- const { snapshot, connection, request } = this.sourceState;
1115
+ const { snapshot, connection, request, failure } = this.sourceState;
1174
1116
  const old = previous?.snapshot;
1175
1117
  const drafts = this.drafts();
1176
1118
  const active = this.active();
@@ -1193,11 +1135,20 @@ var Controller = class {
1193
1135
  );
1194
1136
  const players = sameActors && previous ? previous.players : {
1195
1137
  order: Object.freeze(playerObjects.map((p) => p.id)),
1196
- get: (id) => playerObjects.find((p) => p.id === id),
1138
+ get: (id) => requireLookup(
1139
+ playerObjects.find((p) => p.id === id),
1140
+ "Player",
1141
+ id
1142
+ ),
1143
+ find: (id) => playerObjects.find((p) => p.id === id),
1197
1144
  getAll: () => playerObjects,
1198
1145
  next: (id) => {
1199
1146
  const index = playerObjects.findIndex((p) => p.id === id);
1200
- return index < 0 ? void 0 : playerObjects[(index + 1) % playerObjects.length];
1147
+ return requireLookup(
1148
+ index < 0 ? void 0 : playerObjects[(index + 1) % playerObjects.length],
1149
+ "Next player after",
1150
+ id
1151
+ );
1201
1152
  }
1202
1153
  };
1203
1154
  const me = sameActors && previous ? previous.me : snapshot ? {
@@ -1233,6 +1184,14 @@ var Controller = class {
1233
1184
  this.lastInteractions = Object.freeze(interactionObjects);
1234
1185
  const interactions = interactionsSame ? previous.interactions : {
1235
1186
  get: (key) => {
1187
+ this.observed.add(key);
1188
+ return requireLookup(
1189
+ interactionObjects.find((value) => value.key === key),
1190
+ "Interaction",
1191
+ key
1192
+ );
1193
+ },
1194
+ find: (key) => {
1236
1195
  this.observed.add(key);
1237
1196
  return interactionObjects.find((value) => value.key === key);
1238
1197
  },
@@ -1249,7 +1208,12 @@ var Controller = class {
1249
1208
  }
1250
1209
  };
1251
1210
  const inputs = interactionsSame && previous ? previous.inputs : {
1252
- get: (key, name) => interactions.get(key)?.getInput(name)
1211
+ get: (key, name) => requireLookup(
1212
+ interactions.find(key)?.findInput(name),
1213
+ `Input in interaction ${key}`,
1214
+ name
1215
+ ),
1216
+ find: (key, name) => interactions.find(key)?.findInput(name)
1253
1217
  };
1254
1218
  const zonesSame = sameSnapshot && interactionsSame && previous;
1255
1219
  const zones = zonesSame ? previous.zones : (() => {
@@ -1291,12 +1255,22 @@ var Controller = class {
1291
1255
  )
1292
1256
  );
1293
1257
  return {
1294
- get: (id) => objects.find((zone) => zone.id === id),
1258
+ get: (id) => requireLookup(
1259
+ objects.find((zone) => zone.id === id),
1260
+ "Zone",
1261
+ id
1262
+ ),
1263
+ find: (id) => objects.find((zone) => zone.id === id),
1295
1264
  getAll: () => Object.freeze(objects)
1296
1265
  };
1297
1266
  })();
1298
1267
  const cards = zonesSame ? previous.cards : {
1299
- get: (id) => zones.getAll().map((zone) => zone.getCard(id)).find((card) => card !== void 0)
1268
+ get: (id) => requireLookup(
1269
+ zones.getAll().map((zone) => zone.findCard(id)).find((card) => card !== void 0),
1270
+ "Card",
1271
+ id
1272
+ ),
1273
+ find: (id) => zones.getAll().map((zone) => zone.findCard(id)).find((card) => card !== void 0)
1300
1274
  };
1301
1275
  const capturedState = this.lastSnapshotDrafts === drafts && previous?.state.activeInteraction === active ? previous.state : immutableCopy({ drafts, activeInteraction: active });
1302
1276
  this.lastSnapshotDrafts = drafts;
@@ -1308,6 +1282,7 @@ var Controller = class {
1308
1282
  version: snapshot?.version ?? null,
1309
1283
  connection,
1310
1284
  request,
1285
+ failure,
1311
1286
  state: capturedState,
1312
1287
  phase: sameSnapshot && previous ? previous.phase : Object.freeze(
1313
1288
  new PhaseObject(snapshot?.frame.flow.currentPhase ?? null)
@@ -1360,4 +1335,4 @@ export {
1360
1335
  AmbiguousTargetError,
1361
1336
  createGameInstance
1362
1337
  };
1363
- //# sourceMappingURL=chunk-SP234IY4.js.map
1338
+ //# sourceMappingURL=chunk-OOFMBCFF.js.map