Проблема

У keyval есть апи для редактирования элементов, удаления, добавления и т.д. Например orders.edit.add({id: 'coffee', amount: 2}) это эвент, который добавляет соответствующую запись в список

У keyval есть поддержка моделей для элементов, чтобы добавлять функциональность для конкретного элемента списка. Например у элемента могут быть дополнительные сторы создаваемые через createStore, вычисляемые поля, создаваемые через combine, эвенты и так далее. Если модель переданная в кейвал возвращает в одном из полей вложенный кейвал, то это означает, что в этом поле будет массив.

const menu = keyval({
  key: 'dishId',
  props: {
    dishId: define.store<string>(),
    price: define.store<number>(),
  },
  create({dishId: $dishId}) {
    const additives = keyval({
      key: 'additiveId',
      props: {
		    additiveId: define.store<string>(),
		    price: define.store<number>(),
		  },
    })
    return {additives}
  }
})

// Условно, получаем такой тип значений:
declare const menu.$items: Array<{
  dishId: string
  price: number
  additives: Array<{
    additiveId: string
    price: number
  }>
}>

Поле props в модели означает, что эти данные она получает в качестве входящих (как аргумент конструктора в классах) и на основе них с помощью create достраивает все необходимые вычисляемые поля и эвенты. Эти данные в этой статье называются Input

Подразумевается, что этих данных достаточно, чтобы создать новый инстанс модели, поэтому апи для заполнения данных принимает только их:

declare const menu.edit.add: EventCallable<{
  dishId: string
  price: number
}>

Вопрос

Как заполнять вложенный кейвал в таких условиях?

Вариант 1, ручной

Можно вынести эвенты кейвала в return тела модели, после чего вручную вызывать все эти эвенты для каждого уровня сложности. Вариант крайне не удобный и не рассматривается, нужно иметь возможность заполнить весь кейвал в один вызов эвента

Вариант 2, передача keyval в props

Можно задекларировать наличие кейвала в пропсах через define.keyval (этот апи кстати в любом случае понадобится), но тогда мы лишаемся возможности создавать тело модели для вложенного кейвала. Тогда, как вариант, можно передавать сам кейвал в пропсы

const menu = keyval({
  key: 'dishId',
  props: {
    dishId: define.store<string>(),
    price: define.store<number>(),
    additives: keyval({
      key: 'additiveId',
      props: {
		    additiveId: define.store<string>(),
		    price: define.store<number>(),
		  },
    }),
  },
})

Вариант хороший, но есть один существенный минус — добавлять в тело модели вложенного кейвала сторы из замыкания не выйдет. Например если вложенный кейвал опирается на стор dishId, то это становится невозможным

Вывод: такой подход подойдет для описания кейвалов без интерактивности, но кейс с интерактивными вложенными кейвалами им не охватывается

Вариант 3, отдельный кейс для кейвалов/WritableStore

Можно сделать эдж кейс для кейвалов и когда они добавляются в return модели, то всё равно добавлять их в Input позволяя использовать их при заполнении модели

Но тогда возникает вопрос, а не стоит ли добавить в Input все не readonly сторы которые возвращает модель? У пользователя может быть $count внутри модели и эвент который делает инкремент для него, но возможность задать начальное значение для этого стора выглядит полезным

Такую автоматическую фильтрацию может запретить тайпскрипт, который и так уже находится на грани работоспособности с уже имеющимися в кейвале фичами, но тогда возможно стоит попробовать возвращать из модели объект, в котором сторы в поле {input: {foo: $foo}} будут включены в Input модели