@cardstack/boxel-cli 0.2.0 → 0.3.0-unstable.58

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 (882) hide show
  1. package/bundled-types/base/-private.d.ts +1 -0
  2. package/bundled-types/base/address.d.ts +17 -0
  3. package/bundled-types/base/ai-app-generator.d.ts +14 -0
  4. package/bundled-types/base/amount-with-currency.d.ts +10 -0
  5. package/bundled-types/base/avif-image-def.d.ts +12 -0
  6. package/bundled-types/base/avif-meta-extractor.d.ts +4 -0
  7. package/bundled-types/base/base64-image.d.ts +25 -0
  8. package/bundled-types/base/big-integer.d.ts +12 -0
  9. package/bundled-types/base/boolean.d.ts +14 -0
  10. package/bundled-types/base/brand-functional-palette.d.ts +16 -0
  11. package/bundled-types/base/brand-guide.d.ts +26 -0
  12. package/bundled-types/base/brand-logo.d.ts +29 -0
  13. package/bundled-types/base/card-api.d.ts +513 -0
  14. package/bundled-types/base/card-serialization.d.ts +47 -0
  15. package/bundled-types/base/cards-grid.d.ts +10 -0
  16. package/bundled-types/base/code-ref.d.ts +14 -0
  17. package/bundled-types/base/codemirror-editor.d.ts +135 -0
  18. package/bundled-types/base/color-field/components/advanced-color-picker.d.ts +66 -0
  19. package/bundled-types/base/color-field/components/color-picker-field.d.ts +22 -0
  20. package/bundled-types/base/color-field/components/color-wheel-picker.d.ts +44 -0
  21. package/bundled-types/base/color-field/components/contrast-checker-addon.d.ts +14 -0
  22. package/bundled-types/base/color-field/components/recent-colors-addon.d.ts +19 -0
  23. package/bundled-types/base/color-field/components/slider-picker.d.ts +40 -0
  24. package/bundled-types/base/color-field/components/swatches-picker.d.ts +5 -0
  25. package/bundled-types/base/color-field/modifiers/setup-element-modifier.d.ts +10 -0
  26. package/bundled-types/base/color-field/util/color-field-signature.d.ts +9 -0
  27. package/bundled-types/base/color-field/util/color-utils.d.ts +96 -0
  28. package/bundled-types/base/color-field/util/css-color-parsers.d.ts +11 -0
  29. package/bundled-types/base/color.d.ts +10 -0
  30. package/bundled-types/base/command.d.ts +593 -0
  31. package/bundled-types/base/commands/search-card-result.d.ts +36 -0
  32. package/bundled-types/base/components/age.d.ts +22 -0
  33. package/bundled-types/base/components/business-days.d.ts +16 -0
  34. package/bundled-types/base/components/card-list.d.ts +25 -0
  35. package/bundled-types/base/components/cards-grid-layout.d.ts +58 -0
  36. package/bundled-types/base/components/countdown.d.ts +31 -0
  37. package/bundled-types/base/components/expiration-warning.d.ts +24 -0
  38. package/bundled-types/base/components/time-ago.d.ts +24 -0
  39. package/bundled-types/base/components/time-slots.d.ts +20 -0
  40. package/bundled-types/base/components/timeline.d.ts +22 -0
  41. package/bundled-types/base/contains-many-component.d.ts +7 -0
  42. package/bundled-types/base/coordinate.d.ts +7 -0
  43. package/bundled-types/base/country.d.ts +23 -0
  44. package/bundled-types/base/css-value.d.ts +6 -0
  45. package/bundled-types/base/csv-file-def.d.ts +29 -0
  46. package/bundled-types/base/currency.d.ts +16 -0
  47. package/bundled-types/base/date/day.d.ts +9 -0
  48. package/bundled-types/base/date/month-day.d.ts +10 -0
  49. package/bundled-types/base/date/month-year.d.ts +9 -0
  50. package/bundled-types/base/date/month.d.ts +9 -0
  51. package/bundled-types/base/date/quarter.d.ts +10 -0
  52. package/bundled-types/base/date/week.d.ts +9 -0
  53. package/bundled-types/base/date/year.d.ts +9 -0
  54. package/bundled-types/base/date-range-field.d.ts +12 -0
  55. package/bundled-types/base/date.d.ts +12 -0
  56. package/bundled-types/base/datetime-stamp.d.ts +7 -0
  57. package/bundled-types/base/datetime.d.ts +12 -0
  58. package/bundled-types/base/default-templates/atom.d.ts +10 -0
  59. package/bundled-types/base/default-templates/card-info.d.ts +32 -0
  60. package/bundled-types/base/default-templates/embedded.d.ts +11 -0
  61. package/bundled-types/base/default-templates/field-edit.d.ts +10 -0
  62. package/bundled-types/base/default-templates/file-def-edit.d.ts +8 -0
  63. package/bundled-types/base/default-templates/fitted.d.ts +12 -0
  64. package/bundled-types/base/default-templates/head.d.ts +13 -0
  65. package/bundled-types/base/default-templates/image-def-atom.d.ts +8 -0
  66. package/bundled-types/base/default-templates/image-def-embedded.d.ts +8 -0
  67. package/bundled-types/base/default-templates/image-def-fitted.d.ts +8 -0
  68. package/bundled-types/base/default-templates/image-def-isolated.d.ts +8 -0
  69. package/bundled-types/base/default-templates/isolated-and-edit.d.ts +15 -0
  70. package/bundled-types/base/default-templates/markdown-fallback.d.ts +19 -0
  71. package/bundled-types/base/default-templates/markdown.d.ts +60 -0
  72. package/bundled-types/base/default-templates/missing-template.d.ts +13 -0
  73. package/bundled-types/base/default-templates/theme-dashboard.d.ts +126 -0
  74. package/bundled-types/base/detailed-style-reference.d.ts +48 -0
  75. package/bundled-types/base/email.d.ts +10 -0
  76. package/bundled-types/base/enum.d.ts +30 -0
  77. package/bundled-types/base/ethereum-address.d.ts +13 -0
  78. package/bundled-types/base/field-component.d.ts +65 -0
  79. package/bundled-types/base/field-support.d.ts +55 -0
  80. package/bundled-types/base/file-api.d.ts +2 -0
  81. package/bundled-types/base/file-menu-items.d.ts +4 -0
  82. package/bundled-types/base/gif-image-def.d.ts +12 -0
  83. package/bundled-types/base/gif-meta-extractor.d.ts +4 -0
  84. package/bundled-types/base/gts-file-def.d.ts +7 -0
  85. package/bundled-types/base/helpers/country.d.ts +3198 -0
  86. package/bundled-types/base/helpers/sanitized-html.d.ts +2 -0
  87. package/bundled-types/base/helpers/set-background-image.d.ts +2 -0
  88. package/bundled-types/base/image-file-def.d.ts +1 -0
  89. package/bundled-types/base/image.d.ts +8 -0
  90. package/bundled-types/base/index.d.ts +1 -0
  91. package/bundled-types/base/join-the-community.d.ts +14 -0
  92. package/bundled-types/base/jpg-image-def.d.ts +12 -0
  93. package/bundled-types/base/jpg-meta-extractor.d.ts +4 -0
  94. package/bundled-types/base/json-file-def.d.ts +23 -0
  95. package/bundled-types/base/links-to-editor.d.ts +22 -0
  96. package/bundled-types/base/links-to-many-component.d.ts +7 -0
  97. package/bundled-types/base/llm-model.d.ts +6 -0
  98. package/bundled-types/base/markdown-file-def.d.ts +25 -0
  99. package/bundled-types/base/markdown-helpers.d.ts +26 -0
  100. package/bundled-types/base/markdown.d.ts +1 -0
  101. package/bundled-types/base/matrix-event.d.ts +400 -0
  102. package/bundled-types/base/menu-items.d.ts +21 -0
  103. package/bundled-types/base/number/components/badge-counter.d.ts +32 -0
  104. package/bundled-types/base/number/components/badge-metric.d.ts +34 -0
  105. package/bundled-types/base/number/components/badge-notification.d.ts +38 -0
  106. package/bundled-types/base/number/components/gauge.d.ts +45 -0
  107. package/bundled-types/base/number/components/number-input.d.ts +27 -0
  108. package/bundled-types/base/number/components/progress-bar.d.ts +44 -0
  109. package/bundled-types/base/number/components/progress-circle.d.ts +41 -0
  110. package/bundled-types/base/number/components/score.d.ts +41 -0
  111. package/bundled-types/base/number/components/stat.d.ts +38 -0
  112. package/bundled-types/base/number/util/index.d.ts +29 -0
  113. package/bundled-types/base/number.d.ts +52 -0
  114. package/bundled-types/base/percentage.d.ts +9 -0
  115. package/bundled-types/base/phone-number.d.ts +25 -0
  116. package/bundled-types/base/png-image-def.d.ts +12 -0
  117. package/bundled-types/base/png-meta-extractor.d.ts +4 -0
  118. package/bundled-types/base/positioned-card.d.ts +7 -0
  119. package/bundled-types/base/query-field-support.d.ts +6 -0
  120. package/bundled-types/base/realm-config.d.ts +16 -0
  121. package/bundled-types/base/realm.d.ts +7 -0
  122. package/bundled-types/base/resources/command-data.d.ts +43 -0
  123. package/bundled-types/base/response-field.d.ts +6 -0
  124. package/bundled-types/base/rich-markdown.d.ts +30 -0
  125. package/bundled-types/base/shared-state.d.ts +1 -0
  126. package/bundled-types/base/skill-plus.d.ts +79 -0
  127. package/bundled-types/base/skill-reference.d.ts +12 -0
  128. package/bundled-types/base/skill-set.d.ts +18 -0
  129. package/bundled-types/base/skill.d.ts +21 -0
  130. package/bundled-types/base/spec.d.ts +108 -0
  131. package/bundled-types/base/string.d.ts +2 -0
  132. package/bundled-types/base/structured-theme-variables.d.ts +85 -0
  133. package/bundled-types/base/structured-theme.d.ts +32 -0
  134. package/bundled-types/base/style-reference.d.ts +14 -0
  135. package/bundled-types/base/svg-image-def.d.ts +12 -0
  136. package/bundled-types/base/svg-meta-extractor.d.ts +12 -0
  137. package/bundled-types/base/system-card.d.ts +17 -0
  138. package/bundled-types/base/tag.d.ts +15 -0
  139. package/bundled-types/base/text-area.d.ts +2 -0
  140. package/bundled-types/base/text-file-def.d.ts +23 -0
  141. package/bundled-types/base/text-input-validator.d.ts +13 -0
  142. package/bundled-types/base/theme.d.ts +2 -0
  143. package/bundled-types/base/time/duration.d.ts +14 -0
  144. package/bundled-types/base/time/relative-time.d.ts +10 -0
  145. package/bundled-types/base/time/time-range.d.ts +11 -0
  146. package/bundled-types/base/time.d.ts +9 -0
  147. package/bundled-types/base/ts-file-def.d.ts +26 -0
  148. package/bundled-types/base/ts-highlight.d.ts +1 -0
  149. package/bundled-types/base/types/@cardstack/boxel-host/index.d.ts +5 -0
  150. package/bundled-types/base/types/ember-css-url/index.d.ts +15 -0
  151. package/bundled-types/base/typography.d.ts +14 -0
  152. package/bundled-types/base/url.d.ts +13 -0
  153. package/bundled-types/base/watched-array.d.ts +7 -0
  154. package/bundled-types/base/webp-image-def.d.ts +12 -0
  155. package/bundled-types/base/webp-meta-extractor.d.ts +4 -0
  156. package/bundled-types/base/website.d.ts +8 -0
  157. package/bundled-types/base/welcome-to-boxel.d.ts +22 -0
  158. package/bundled-types/boxel-ui/components/accordion/index.gts +50 -0
  159. package/bundled-types/boxel-ui/components/accordion/item/index.gts +156 -0
  160. package/bundled-types/boxel-ui/components/accordion/usage.gts +157 -0
  161. package/bundled-types/boxel-ui/components/add-button/index.gts +46 -0
  162. package/bundled-types/boxel-ui/components/add-button/usage.gts +54 -0
  163. package/bundled-types/boxel-ui/components/alert/index.gts +151 -0
  164. package/bundled-types/boxel-ui/components/alert/usage.gts +66 -0
  165. package/bundled-types/boxel-ui/components/avatar/index.gts +79 -0
  166. package/bundled-types/boxel-ui/components/avatar/usage.gts +96 -0
  167. package/bundled-types/boxel-ui/components/basic-fitted/index.gts +340 -0
  168. package/bundled-types/boxel-ui/components/basic-fitted/usage.gts +223 -0
  169. package/bundled-types/boxel-ui/components/button/index.gts +416 -0
  170. package/bundled-types/boxel-ui/components/button/usage.gts +334 -0
  171. package/bundled-types/boxel-ui/components/card-container/index.gts +251 -0
  172. package/bundled-types/boxel-ui/components/card-container/usage.gts +53 -0
  173. package/bundled-types/boxel-ui/components/card-header/index.gts +473 -0
  174. package/bundled-types/boxel-ui/components/card-header/usage.gts +295 -0
  175. package/bundled-types/boxel-ui/components/circle-spinner/index.gts +42 -0
  176. package/bundled-types/boxel-ui/components/circle-spinner/usage.gts +48 -0
  177. package/bundled-types/boxel-ui/components/color-palette/index.gts +134 -0
  178. package/bundled-types/boxel-ui/components/color-palette/usage.gts +69 -0
  179. package/bundled-types/boxel-ui/components/color-picker/index.gts +125 -0
  180. package/bundled-types/boxel-ui/components/color-picker/usage.gts +56 -0
  181. package/bundled-types/boxel-ui/components/container/index.gts +67 -0
  182. package/bundled-types/boxel-ui/components/container/usage.gts +59 -0
  183. package/bundled-types/boxel-ui/components/context-button/index.gts +184 -0
  184. package/bundled-types/boxel-ui/components/context-button/usage.gts +125 -0
  185. package/bundled-types/boxel-ui/components/copy-button/index.gts +79 -0
  186. package/bundled-types/boxel-ui/components/copy-button/usage.gts +25 -0
  187. package/bundled-types/boxel-ui/components/date-range-picker/index.gts +211 -0
  188. package/bundled-types/boxel-ui/components/date-range-picker/setup.gts +11 -0
  189. package/bundled-types/boxel-ui/components/date-range-picker/usage.gts +52 -0
  190. package/bundled-types/boxel-ui/components/drag-and-drop/index.gts +256 -0
  191. package/bundled-types/boxel-ui/components/drag-and-drop/usage.gts +142 -0
  192. package/bundled-types/boxel-ui/components/dropdown/index.gts +478 -0
  193. package/bundled-types/boxel-ui/components/dropdown/trigger/index.gts +70 -0
  194. package/bundled-types/boxel-ui/components/dropdown/trigger/usage.gts +54 -0
  195. package/bundled-types/boxel-ui/components/dropdown/usage.gts +119 -0
  196. package/bundled-types/boxel-ui/components/email-input/index.gts +111 -0
  197. package/bundled-types/boxel-ui/components/email-input/usage.gts +75 -0
  198. package/bundled-types/boxel-ui/components/entity-icon-display/index.gts +144 -0
  199. package/bundled-types/boxel-ui/components/entity-icon-display/usage.gts +54 -0
  200. package/bundled-types/boxel-ui/components/entity-thumbnail-display/index.gts +148 -0
  201. package/bundled-types/boxel-ui/components/entity-thumbnail-display/usage.gts +76 -0
  202. package/bundled-types/boxel-ui/components/field-container/index.gts +150 -0
  203. package/bundled-types/boxel-ui/components/field-container/usage.gts +188 -0
  204. package/bundled-types/boxel-ui/components/filter-list/index.gts +255 -0
  205. package/bundled-types/boxel-ui/components/filter-list/usage.gts +153 -0
  206. package/bundled-types/boxel-ui/components/fitted-card-container/index.gts +66 -0
  207. package/bundled-types/boxel-ui/components/fitted-card-container/usage.gts +81 -0
  208. package/bundled-types/boxel-ui/components/grid-container/grid-item-container/index.gts +37 -0
  209. package/bundled-types/boxel-ui/components/grid-container/index.gts +116 -0
  210. package/bundled-types/boxel-ui/components/grid-container/usage.gts +105 -0
  211. package/bundled-types/boxel-ui/components/header/index.gts +101 -0
  212. package/bundled-types/boxel-ui/components/header/usage.gts +116 -0
  213. package/bundled-types/boxel-ui/components/icon-button/index.gts +148 -0
  214. package/bundled-types/boxel-ui/components/icon-button/usage.gts +249 -0
  215. package/bundled-types/boxel-ui/components/input/index.gts +560 -0
  216. package/bundled-types/boxel-ui/components/input/usage.gts +449 -0
  217. package/bundled-types/boxel-ui/components/input-group/accessories/index.gts +191 -0
  218. package/bundled-types/boxel-ui/components/input-group/controls/index.gts +54 -0
  219. package/bundled-types/boxel-ui/components/input-group/index.gts +347 -0
  220. package/bundled-types/boxel-ui/components/input-group/usage.gts +437 -0
  221. package/bundled-types/boxel-ui/components/kanban/card.gts +89 -0
  222. package/bundled-types/boxel-ui/components/kanban/column-header.gts +151 -0
  223. package/bundled-types/boxel-ui/components/kanban/drag.gts +1007 -0
  224. package/bundled-types/boxel-ui/components/kanban/engine.ts +247 -0
  225. package/bundled-types/boxel-ui/components/kanban/ghost.gts +64 -0
  226. package/bundled-types/boxel-ui/components/kanban/index.gts +16 -0
  227. package/bundled-types/boxel-ui/components/kanban/modifiers.gts +42 -0
  228. package/bundled-types/boxel-ui/components/kanban/plane-inner.gts +525 -0
  229. package/bundled-types/boxel-ui/components/kanban/plane.gts +163 -0
  230. package/bundled-types/boxel-ui/components/kanban/usage.gts +392 -0
  231. package/bundled-types/boxel-ui/components/label/index.gts +64 -0
  232. package/bundled-types/boxel-ui/components/loading-indicator/index.gts +78 -0
  233. package/bundled-types/boxel-ui/components/loading-indicator/usage.gts +72 -0
  234. package/bundled-types/boxel-ui/components/menu/index.gts +267 -0
  235. package/bundled-types/boxel-ui/components/menu/usage.gts +100 -0
  236. package/bundled-types/boxel-ui/components/message/index.gts +146 -0
  237. package/bundled-types/boxel-ui/components/message/usage.gts +263 -0
  238. package/bundled-types/boxel-ui/components/modal/index.gts +159 -0
  239. package/bundled-types/boxel-ui/components/modal/usage.gts +206 -0
  240. package/bundled-types/boxel-ui/components/multi-select/after-options.gts +59 -0
  241. package/bundled-types/boxel-ui/components/multi-select/index.gts +230 -0
  242. package/bundled-types/boxel-ui/components/multi-select/selected-item.gts +91 -0
  243. package/bundled-types/boxel-ui/components/multi-select/trigger.gts +190 -0
  244. package/bundled-types/boxel-ui/components/multi-select/usage.gts +420 -0
  245. package/bundled-types/boxel-ui/components/phone-input/index.gts +149 -0
  246. package/bundled-types/boxel-ui/components/phone-input/usage.gts +73 -0
  247. package/bundled-types/boxel-ui/components/picker/before-options-with-search.gts +340 -0
  248. package/bundled-types/boxel-ui/components/picker/index.gts +427 -0
  249. package/bundled-types/boxel-ui/components/picker/option-row.gts +270 -0
  250. package/bundled-types/boxel-ui/components/picker/selected-item.gts +171 -0
  251. package/bundled-types/boxel-ui/components/picker/trigger-labeled.gts +216 -0
  252. package/bundled-types/boxel-ui/components/picker/usage.gts +179 -0
  253. package/bundled-types/boxel-ui/components/pill/index.gts +231 -0
  254. package/bundled-types/boxel-ui/components/pill/usage.gts +230 -0
  255. package/bundled-types/boxel-ui/components/progress-bar/index.gts +124 -0
  256. package/bundled-types/boxel-ui/components/progress-bar/usage.gts +131 -0
  257. package/bundled-types/boxel-ui/components/progress-radial/index.gts +95 -0
  258. package/bundled-types/boxel-ui/components/progress-radial/usage.gts +79 -0
  259. package/bundled-types/boxel-ui/components/radio-input/index.gts +143 -0
  260. package/bundled-types/boxel-ui/components/radio-input/item.gts +210 -0
  261. package/bundled-types/boxel-ui/components/radio-input/usage.gts +303 -0
  262. package/bundled-types/boxel-ui/components/realm-icon/index.gts +129 -0
  263. package/bundled-types/boxel-ui/components/realm-icon/usage.gts +62 -0
  264. package/bundled-types/boxel-ui/components/resizable-panel-group/handle.gts +180 -0
  265. package/bundled-types/boxel-ui/components/resizable-panel-group/index.gts +341 -0
  266. package/bundled-types/boxel-ui/components/resizable-panel-group/panel.gts +86 -0
  267. package/bundled-types/boxel-ui/components/resizable-panel-group/usage.gts +350 -0
  268. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/adjust-layout-by-delta.ts +231 -0
  269. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/assert.ts +10 -0
  270. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/calculate-delta-percentage.ts +41 -0
  271. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/calculate-unsafe-default-layout.ts +53 -0
  272. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/compare-layouts.ts +12 -0
  273. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/const.ts +6 -0
  274. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/determine-pivot-indices.ts +15 -0
  275. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/dom/get-panel-element.ts +10 -0
  276. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/dom/get-panel-elements-for-group.ts +8 -0
  277. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/dom/get-panel-group-element.ts +21 -0
  278. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/dom/get-resize-handle-element-index.ts +14 -0
  279. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/dom/get-resize-handle-element.ts +12 -0
  280. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/dom/get-resize-handle-elements-for-group.ts +10 -0
  281. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/fuzzy-layouts-equal.ts +22 -0
  282. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/fuzzy-numbers.ts +21 -0
  283. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/get-resize-event-coordinates.ts +8 -0
  284. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/get-resize-event-cursor-position.ts +10 -0
  285. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/panel-resize-handle-registry.ts +446 -0
  286. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/resize-panel.ts +35 -0
  287. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/types.ts +23 -0
  288. package/bundled-types/boxel-ui/components/resizable-panel-group/utils/validate-panel-group-layout.ts +86 -0
  289. package/bundled-types/boxel-ui/components/select/index.gts +882 -0
  290. package/bundled-types/boxel-ui/components/select/trigger.gts +132 -0
  291. package/bundled-types/boxel-ui/components/select/usage.gts +353 -0
  292. package/bundled-types/boxel-ui/components/skeleton-placeholder/index.gts +89 -0
  293. package/bundled-types/boxel-ui/components/skeleton-placeholder/usage.gts +102 -0
  294. package/bundled-types/boxel-ui/components/sort-dropdown/index.gts +91 -0
  295. package/bundled-types/boxel-ui/components/sort-dropdown/usage.gts +77 -0
  296. package/bundled-types/boxel-ui/components/swatch/index.gts +81 -0
  297. package/bundled-types/boxel-ui/components/swatch/usage.gts +57 -0
  298. package/bundled-types/boxel-ui/components/switch/index.gts +95 -0
  299. package/bundled-types/boxel-ui/components/switch/usage.gts +79 -0
  300. package/bundled-types/boxel-ui/components/tabbed-header/index.gts +141 -0
  301. package/bundled-types/boxel-ui/components/tabbed-header/usage.gts +103 -0
  302. package/bundled-types/boxel-ui/components/tag/index.gts +50 -0
  303. package/bundled-types/boxel-ui/components/tag/usage.gts +89 -0
  304. package/bundled-types/boxel-ui/components/tag-list/index.gts +85 -0
  305. package/bundled-types/boxel-ui/components/tag-list/usage.gts +129 -0
  306. package/bundled-types/boxel-ui/components/tooltip/index.gts +306 -0
  307. package/bundled-types/boxel-ui/components/tooltip/usage.gts +168 -0
  308. package/bundled-types/boxel-ui/components/view-selector/index.gts +166 -0
  309. package/bundled-types/boxel-ui/components/view-selector/usage.gts +149 -0
  310. package/bundled-types/boxel-ui/components.ts +169 -0
  311. package/bundled-types/boxel-ui/helpers/add-class-to-svg.ts +8 -0
  312. package/bundled-types/boxel-ui/helpers/clipboard.ts +21 -0
  313. package/bundled-types/boxel-ui/helpers/cn.ts +12 -0
  314. package/bundled-types/boxel-ui/helpers/color-tools.ts +331 -0
  315. package/bundled-types/boxel-ui/helpers/compact.ts +3 -0
  316. package/bundled-types/boxel-ui/helpers/contrast-color.ts +32 -0
  317. package/bundled-types/boxel-ui/helpers/css-var.ts +41 -0
  318. package/bundled-types/boxel-ui/helpers/currency-format.ts +25 -0
  319. package/bundled-types/boxel-ui/helpers/dayjs-format.ts +27 -0
  320. package/bundled-types/boxel-ui/helpers/deterministic-color-from-string.ts +45 -0
  321. package/bundled-types/boxel-ui/helpers/element.ts +16 -0
  322. package/bundled-types/boxel-ui/helpers/extract-css-variables.ts +126 -0
  323. package/bundled-types/boxel-ui/helpers/format-age.ts +72 -0
  324. package/bundled-types/boxel-ui/helpers/format-countdown.ts +110 -0
  325. package/bundled-types/boxel-ui/helpers/format-currency.ts +95 -0
  326. package/bundled-types/boxel-ui/helpers/format-date-time.ts +567 -0
  327. package/bundled-types/boxel-ui/helpers/format-duration.ts +143 -0
  328. package/bundled-types/boxel-ui/helpers/format-file-size.ts +49 -0
  329. package/bundled-types/boxel-ui/helpers/format-list.ts +73 -0
  330. package/bundled-types/boxel-ui/helpers/format-names.ts +103 -0
  331. package/bundled-types/boxel-ui/helpers/format-number.ts +140 -0
  332. package/bundled-types/boxel-ui/helpers/format-ordinal.ts +115 -0
  333. package/bundled-types/boxel-ui/helpers/format-period.ts +188 -0
  334. package/bundled-types/boxel-ui/helpers/format-relative-time.ts +789 -0
  335. package/bundled-types/boxel-ui/helpers/generate-css-variables.ts +47 -0
  336. package/bundled-types/boxel-ui/helpers/markdown-escape.ts +36 -0
  337. package/bundled-types/boxel-ui/helpers/math-helpers.ts +18 -0
  338. package/bundled-types/boxel-ui/helpers/menu-divider.ts +15 -0
  339. package/bundled-types/boxel-ui/helpers/menu-item.ts +76 -0
  340. package/bundled-types/boxel-ui/helpers/optional.ts +7 -0
  341. package/bundled-types/boxel-ui/helpers/pick.ts +11 -0
  342. package/bundled-types/boxel-ui/helpers/sanitize-html.ts +45 -0
  343. package/bundled-types/boxel-ui/helpers/string.ts +15 -0
  344. package/bundled-types/boxel-ui/helpers/theme-css.ts +148 -0
  345. package/bundled-types/boxel-ui/helpers/truth-helpers.ts +48 -0
  346. package/bundled-types/boxel-ui/helpers/validate-email-format.ts +138 -0
  347. package/bundled-types/boxel-ui/helpers/validate-phone-format.ts +201 -0
  348. package/bundled-types/boxel-ui/helpers.ts +134 -0
  349. package/bundled-types/boxel-ui/icons/ai-bw.gts +22 -0
  350. package/bundled-types/boxel-ui/icons/arrow-left.gts +25 -0
  351. package/bundled-types/boxel-ui/icons/arrow-right.gts +28 -0
  352. package/bundled-types/boxel-ui/icons/arrow-top-left.gts +22 -0
  353. package/bundled-types/boxel-ui/icons/arrow-up.gts +25 -0
  354. package/bundled-types/boxel-ui/icons/atom.gts +26 -0
  355. package/bundled-types/boxel-ui/icons/boxel-icon-with-text.gts +29 -0
  356. package/bundled-types/boxel-ui/icons/boxel-icon.gts +34 -0
  357. package/bundled-types/boxel-ui/icons/card-definition.gts +26 -0
  358. package/bundled-types/boxel-ui/icons/card-instance.gts +26 -0
  359. package/bundled-types/boxel-ui/icons/card.gts +24 -0
  360. package/bundled-types/boxel-ui/icons/cardbot-lg.gts +24 -0
  361. package/bundled-types/boxel-ui/icons/caret-down.gts +23 -0
  362. package/bundled-types/boxel-ui/icons/caret-up.gts +23 -0
  363. package/bundled-types/boxel-ui/icons/check-mark.gts +23 -0
  364. package/bundled-types/boxel-ui/icons/chevron-right.gts +23 -0
  365. package/bundled-types/boxel-ui/icons/copy.gts +28 -0
  366. package/bundled-types/boxel-ui/icons/diagonal-arrow-left-up.gts +21 -0
  367. package/bundled-types/boxel-ui/icons/download.gts +19 -0
  368. package/bundled-types/boxel-ui/icons/dropdown-arrow-down.gts +20 -0
  369. package/bundled-types/boxel-ui/icons/dropdown-arrow-filled.gts +19 -0
  370. package/bundled-types/boxel-ui/icons/dropdown-arrow-up.gts +20 -0
  371. package/bundled-types/boxel-ui/icons/edit.gts +14 -0
  372. package/bundled-types/boxel-ui/icons/embedded.gts +34 -0
  373. package/bundled-types/boxel-ui/icons/exclamation-circle.gts +26 -0
  374. package/bundled-types/boxel-ui/icons/exclamation.gts +20 -0
  375. package/bundled-types/boxel-ui/icons/eye.gts +19 -0
  376. package/bundled-types/boxel-ui/icons/failure-bordered.gts +26 -0
  377. package/bundled-types/boxel-ui/icons/field.gts +26 -0
  378. package/bundled-types/boxel-ui/icons/file-alert.gts +28 -0
  379. package/bundled-types/boxel-ui/icons/file.gts +27 -0
  380. package/bundled-types/boxel-ui/icons/fitted.gts +42 -0
  381. package/bundled-types/boxel-ui/icons/folder.gts +26 -0
  382. package/bundled-types/boxel-ui/icons/form.gts +38 -0
  383. package/bundled-types/boxel-ui/icons/four-lines.gts +21 -0
  384. package/bundled-types/boxel-ui/icons/grid-3x3.gts +26 -0
  385. package/bundled-types/boxel-ui/icons/group.gts +22 -0
  386. package/bundled-types/boxel-ui/icons/head.gts +28 -0
  387. package/bundled-types/boxel-ui/icons/highlight-icon.gts +46 -0
  388. package/bundled-types/boxel-ui/icons/icon-circle-selected.gts +25 -0
  389. package/bundled-types/boxel-ui/icons/icon-circle.gts +25 -0
  390. package/bundled-types/boxel-ui/icons/icon-code.gts +22 -0
  391. package/bundled-types/boxel-ui/icons/icon-funnel.gts +23 -0
  392. package/bundled-types/boxel-ui/icons/icon-globe.gts +19 -0
  393. package/bundled-types/boxel-ui/icons/icon-grid.gts +68 -0
  394. package/bundled-types/boxel-ui/icons/icon-hexagon.gts +22 -0
  395. package/bundled-types/boxel-ui/icons/icon-inherit.gts +19 -0
  396. package/bundled-types/boxel-ui/icons/icon-link.gts +20 -0
  397. package/bundled-types/boxel-ui/icons/icon-list.gts +40 -0
  398. package/bundled-types/boxel-ui/icons/icon-minus-circle.gts +26 -0
  399. package/bundled-types/boxel-ui/icons/icon-pencil-crossed-out.gts +33 -0
  400. package/bundled-types/boxel-ui/icons/icon-pencil-not-crossed-out.gts +21 -0
  401. package/bundled-types/boxel-ui/icons/icon-pencil.gts +23 -0
  402. package/bundled-types/boxel-ui/icons/icon-plus-circle.gts +31 -0
  403. package/bundled-types/boxel-ui/icons/icon-plus-thin.gts +19 -0
  404. package/bundled-types/boxel-ui/icons/icon-plus.gts +20 -0
  405. package/bundled-types/boxel-ui/icons/icon-search-thick.gts +27 -0
  406. package/bundled-types/boxel-ui/icons/icon-search.gts +25 -0
  407. package/bundled-types/boxel-ui/icons/icon-table.gts +44 -0
  408. package/bundled-types/boxel-ui/icons/icon-trash.gts +24 -0
  409. package/bundled-types/boxel-ui/icons/icon-turn-down-right.gts +18 -0
  410. package/bundled-types/boxel-ui/icons/icon-x.gts +21 -0
  411. package/bundled-types/boxel-ui/icons/image-placeholder.gts +30 -0
  412. package/bundled-types/boxel-ui/icons/isolated.gts +20 -0
  413. package/bundled-types/boxel-ui/icons/loading-indicator.gts +26 -0
  414. package/bundled-types/boxel-ui/icons/lock.gts +23 -0
  415. package/bundled-types/boxel-ui/icons/markdown.gts +19 -0
  416. package/bundled-types/boxel-ui/icons/profile.gts +23 -0
  417. package/bundled-types/boxel-ui/icons/publish-site-icon.gts +25 -0
  418. package/bundled-types/boxel-ui/icons/rows-4.gts +26 -0
  419. package/bundled-types/boxel-ui/icons/select-all.gts +25 -0
  420. package/bundled-types/boxel-ui/icons/send.gts +21 -0
  421. package/bundled-types/boxel-ui/icons/sparkle.gts +21 -0
  422. package/bundled-types/boxel-ui/icons/star-filled.gts +25 -0
  423. package/bundled-types/boxel-ui/icons/star-half-fill.gts +27 -0
  424. package/bundled-types/boxel-ui/icons/star.gts +25 -0
  425. package/bundled-types/boxel-ui/icons/success-bordered.gts +30 -0
  426. package/bundled-types/boxel-ui/icons/three-dots-horizontal.gts +21 -0
  427. package/bundled-types/boxel-ui/icons/triangle-left.gts +23 -0
  428. package/bundled-types/boxel-ui/icons/triangle-right.gts +23 -0
  429. package/bundled-types/boxel-ui/icons/types.ts +10 -0
  430. package/bundled-types/boxel-ui/icons/upload.gts +20 -0
  431. package/bundled-types/boxel-ui/icons/warning.gts +19 -0
  432. package/bundled-types/boxel-ui/icons.gts +246 -0
  433. package/bundled-types/boxel-ui/modifiers/auto-focus.ts +7 -0
  434. package/bundled-types/boxel-ui/modifiers/set-css-var.ts +24 -0
  435. package/bundled-types/boxel-ui/modifiers.ts +14 -0
  436. package/bundled-types/boxel-ui/types/@cardstack/boxel-ui/index.d.ts +3 -0
  437. package/bundled-types/boxel-ui/types/ember-css-url/index.d.ts +15 -0
  438. package/bundled-types/boxel-ui/types/ember-draggable-modifiers/index.d.ts +109 -0
  439. package/bundled-types/boxel-ui/types/ember-draggable-modifiers/utils.d.ts +7 -0
  440. package/bundled-types/boxel-ui/types/ember-focus-trap/index.d.ts +20 -0
  441. package/bundled-types/boxel-ui/types/ember-power-calendar/components/index.d.ts +9 -0
  442. package/bundled-types/boxel-ui/types/ember-resize-modifier/index.d.ts +14 -0
  443. package/bundled-types/boxel-ui/types/ember-set-body-class/index.d.ts +12 -0
  444. package/bundled-types/boxel-ui/types/ember-sortable/index.d.ts +52 -0
  445. package/bundled-types/boxel-ui/types/global.d.ts +7 -0
  446. package/bundled-types/boxel-ui/usage.ts +112 -0
  447. package/bundled-types/boxel-ui/utils/date-utils.ts +192 -0
  448. package/bundled-types/boxel-ui/utils/fitted-formats.ts +161 -0
  449. package/bundled-types/host-app/app.ts +22 -0
  450. package/bundled-types/host-app/commands/add-field-to-card-definition.ts +74 -0
  451. package/bundled-types/host-app/commands/ai-assistant.ts +203 -0
  452. package/bundled-types/host-app/commands/apply-markdown-edit.ts +203 -0
  453. package/bundled-types/host-app/commands/apply-search-replace-block.ts +225 -0
  454. package/bundled-types/host-app/commands/ask-ai.ts +66 -0
  455. package/bundled-types/host-app/commands/authed-fetch.ts +53 -0
  456. package/bundled-types/host-app/commands/bot-requests/create-listing-pr-request.ts +77 -0
  457. package/bundled-types/host-app/commands/bot-requests/send-bot-trigger-event.ts +53 -0
  458. package/bundled-types/host-app/commands/can-read-realm.ts +34 -0
  459. package/bundled-types/host-app/commands/cancel-indexing-job.ts +29 -0
  460. package/bundled-types/host-app/commands/check-correctness.ts +309 -0
  461. package/bundled-types/host-app/commands/copy-and-edit.ts +311 -0
  462. package/bundled-types/host-app/commands/copy-card-as-markdown.ts +41 -0
  463. package/bundled-types/host-app/commands/copy-card-to-stack.ts +70 -0
  464. package/bundled-types/host-app/commands/copy-card.ts +67 -0
  465. package/bundled-types/host-app/commands/copy-file-to-realm.ts +82 -0
  466. package/bundled-types/host-app/commands/copy-source.ts +46 -0
  467. package/bundled-types/host-app/commands/create-ai-assistant-room.ts +144 -0
  468. package/bundled-types/host-app/commands/create-and-open-submission-workflow-card.ts +30 -0
  469. package/bundled-types/host-app/commands/create-specs.ts +422 -0
  470. package/bundled-types/host-app/commands/create-submission-workflow.ts +172 -0
  471. package/bundled-types/host-app/commands/evaluate-module.ts +119 -0
  472. package/bundled-types/host-app/commands/execute-atomic-operations.ts +44 -0
  473. package/bundled-types/host-app/commands/fetch-card-json.ts +33 -0
  474. package/bundled-types/host-app/commands/full-reindex-realm.ts +30 -0
  475. package/bundled-types/host-app/commands/generate-example-cards.ts +298 -0
  476. package/bundled-types/host-app/commands/generate-readme-spec.ts +116 -0
  477. package/bundled-types/host-app/commands/generate-theme-example.ts +147 -0
  478. package/bundled-types/host-app/commands/generate-thumbnail.ts +247 -0
  479. package/bundled-types/host-app/commands/get-all-realm-metas.ts +38 -0
  480. package/bundled-types/host-app/commands/get-available-realm-identifiers.ts +29 -0
  481. package/bundled-types/host-app/commands/get-card-type-schema.ts +59 -0
  482. package/bundled-types/host-app/commands/get-card.ts +34 -0
  483. package/bundled-types/host-app/commands/get-catalog-realm-identifiers.ts +29 -0
  484. package/bundled-types/host-app/commands/get-default-writable-realm.ts +28 -0
  485. package/bundled-types/host-app/commands/get-events-from-room.ts +70 -0
  486. package/bundled-types/host-app/commands/get-realm-of-resource-identifier.ts +37 -0
  487. package/bundled-types/host-app/commands/get-user-system-card.ts +38 -0
  488. package/bundled-types/host-app/commands/index.ts +590 -0
  489. package/bundled-types/host-app/commands/instantiate-card.ts +185 -0
  490. package/bundled-types/host-app/commands/invalidate-realm-identifiers.ts +33 -0
  491. package/bundled-types/host-app/commands/invite-user-to-room.ts +34 -0
  492. package/bundled-types/host-app/commands/lint-and-fix.ts +60 -0
  493. package/bundled-types/host-app/commands/listing-action-build.ts +79 -0
  494. package/bundled-types/host-app/commands/listing-action-init.ts +106 -0
  495. package/bundled-types/host-app/commands/listing-create.ts +632 -0
  496. package/bundled-types/host-app/commands/listing-generate-example.ts +83 -0
  497. package/bundled-types/host-app/commands/listing-install.ts +227 -0
  498. package/bundled-types/host-app/commands/listing-remix.ts +150 -0
  499. package/bundled-types/host-app/commands/listing-update-specs.ts +116 -0
  500. package/bundled-types/host-app/commands/listing-use.ts +97 -0
  501. package/bundled-types/host-app/commands/one-shot-llm-request.ts +209 -0
  502. package/bundled-types/host-app/commands/open-ai-assistant-room.ts +38 -0
  503. package/bundled-types/host-app/commands/open-create-listing-modal.ts +52 -0
  504. package/bundled-types/host-app/commands/open-in-interact-mode.ts +36 -0
  505. package/bundled-types/host-app/commands/open-workspace.ts +38 -0
  506. package/bundled-types/host-app/commands/patch-card-instance.ts +108 -0
  507. package/bundled-types/host-app/commands/patch-code.ts +243 -0
  508. package/bundled-types/host-app/commands/patch-fields.ts +304 -0
  509. package/bundled-types/host-app/commands/patch-theme.ts +52 -0
  510. package/bundled-types/host-app/commands/persist-module-inspector-view.ts +41 -0
  511. package/bundled-types/host-app/commands/populate-with-sample-data.ts +84 -0
  512. package/bundled-types/host-app/commands/preview-format.ts +52 -0
  513. package/bundled-types/host-app/commands/read-binary-file.ts +62 -0
  514. package/bundled-types/host-app/commands/read-card-for-ai-assistant.ts +50 -0
  515. package/bundled-types/host-app/commands/read-file-for-ai-assistant.ts +46 -0
  516. package/bundled-types/host-app/commands/read-source.ts +46 -0
  517. package/bundled-types/host-app/commands/read-text-file.ts +44 -0
  518. package/bundled-types/host-app/commands/register-bot.ts +42 -0
  519. package/bundled-types/host-app/commands/reindex-realm.ts +30 -0
  520. package/bundled-types/host-app/commands/retry-submission-workflow.ts +119 -0
  521. package/bundled-types/host-app/commands/sanitize-module-list.ts +83 -0
  522. package/bundled-types/host-app/commands/save-card.ts +44 -0
  523. package/bundled-types/host-app/commands/screenshot-card.ts +148 -0
  524. package/bundled-types/host-app/commands/search-and-choose.ts +180 -0
  525. package/bundled-types/host-app/commands/search-cards.ts +102 -0
  526. package/bundled-types/host-app/commands/search-google-images.ts +100 -0
  527. package/bundled-types/host-app/commands/send-ai-assistant-message.ts +122 -0
  528. package/bundled-types/host-app/commands/send-request-via-proxy.ts +70 -0
  529. package/bundled-types/host-app/commands/serialize-card.ts +45 -0
  530. package/bundled-types/host-app/commands/set-active-llm.ts +39 -0
  531. package/bundled-types/host-app/commands/set-user-system-card.ts +29 -0
  532. package/bundled-types/host-app/commands/show-card.ts +102 -0
  533. package/bundled-types/host-app/commands/show-file.ts +39 -0
  534. package/bundled-types/host-app/commands/store-add.ts +37 -0
  535. package/bundled-types/host-app/commands/summarize-session.ts +82 -0
  536. package/bundled-types/host-app/commands/switch-submode.ts +112 -0
  537. package/bundled-types/host-app/commands/sync-openrouter-models.ts +373 -0
  538. package/bundled-types/host-app/commands/transform-cards.ts +59 -0
  539. package/bundled-types/host-app/commands/unregister-bot.ts +29 -0
  540. package/bundled-types/host-app/commands/update-code-path-with-selection.ts +45 -0
  541. package/bundled-types/host-app/commands/update-playground-selection.ts +37 -0
  542. package/bundled-types/host-app/commands/update-room-skills.ts +231 -0
  543. package/bundled-types/host-app/commands/utils.ts +178 -0
  544. package/bundled-types/host-app/commands/validate-realm.ts +40 -0
  545. package/bundled-types/host-app/commands/write-binary-file.ts +142 -0
  546. package/bundled-types/host-app/commands/write-text-file.ts +104 -0
  547. package/bundled-types/host-app/components/.gitkeep +0 -0
  548. package/bundled-types/host-app/components/ai-assistant/action-bar.gts +188 -0
  549. package/bundled-types/host-app/components/ai-assistant/ai-assist-button-active-bg.webp +0 -0
  550. package/bundled-types/host-app/components/ai-assistant/ai-assist-button-active-bg@2x.webp +0 -0
  551. package/bundled-types/host-app/components/ai-assistant/ai-assist-button-active-bg@3x.webp +0 -0
  552. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon-animated.webp +0 -0
  553. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon-bw.png +0 -0
  554. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon-bw@2x.png +0 -0
  555. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon-bw@3x.png +0 -0
  556. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon.webp +0 -0
  557. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon@2x.webp +0 -0
  558. package/bundled-types/host-app/components/ai-assistant/ai-assist-icon@3x.webp +0 -0
  559. package/bundled-types/host-app/components/ai-assistant/apply-button/index.gts +226 -0
  560. package/bundled-types/host-app/components/ai-assistant/apply-button/usage.gts +81 -0
  561. package/bundled-types/host-app/components/ai-assistant/attached-file-dropdown-menu.gts +179 -0
  562. package/bundled-types/host-app/components/ai-assistant/attachment-picker/attach-button.gts +176 -0
  563. package/bundled-types/host-app/components/ai-assistant/attachment-picker/attached-items.gts +271 -0
  564. package/bundled-types/host-app/components/ai-assistant/attachment-picker/index.gts +144 -0
  565. package/bundled-types/host-app/components/ai-assistant/attachment-picker/usage.gts +127 -0
  566. package/bundled-types/host-app/components/ai-assistant/button.gts +60 -0
  567. package/bundled-types/host-app/components/ai-assistant/chat-input/index.gts +203 -0
  568. package/bundled-types/host-app/components/ai-assistant/chat-input/usage.gts +68 -0
  569. package/bundled-types/host-app/components/ai-assistant/code-block/actions.gts +58 -0
  570. package/bundled-types/host-app/components/ai-assistant/code-block/apply-code-patch-button.gts +66 -0
  571. package/bundled-types/host-app/components/ai-assistant/code-block/command-header.gts +115 -0
  572. package/bundled-types/host-app/components/ai-assistant/code-block/diff-editor-header.gts +209 -0
  573. package/bundled-types/host-app/components/ai-assistant/code-block/index.gts +228 -0
  574. package/bundled-types/host-app/components/ai-assistant/code-block/patch-footer.gts +32 -0
  575. package/bundled-types/host-app/components/ai-assistant/code-block/view-code-button.gts +73 -0
  576. package/bundled-types/host-app/components/ai-assistant/focus-pill/index.gts +60 -0
  577. package/bundled-types/host-app/components/ai-assistant/focus-pill/usage.gts +45 -0
  578. package/bundled-types/host-app/components/ai-assistant/llm-mode-toggle.gts +113 -0
  579. package/bundled-types/host-app/components/ai-assistant/llm-select.gts +158 -0
  580. package/bundled-types/host-app/components/ai-assistant/message/aibot-message.gts +374 -0
  581. package/bundled-types/host-app/components/ai-assistant/message/attachments.gts +153 -0
  582. package/bundled-types/host-app/components/ai-assistant/message/index.gts +678 -0
  583. package/bundled-types/host-app/components/ai-assistant/message/meta.gts +66 -0
  584. package/bundled-types/host-app/components/ai-assistant/message/text-content.gts +32 -0
  585. package/bundled-types/host-app/components/ai-assistant/message/usage.gts +182 -0
  586. package/bundled-types/host-app/components/ai-assistant/message/user-message.gts +52 -0
  587. package/bundled-types/host-app/components/ai-assistant/new-session-button.gts +126 -0
  588. package/bundled-types/host-app/components/ai-assistant/new-session-settings.gts +180 -0
  589. package/bundled-types/host-app/components/ai-assistant/new-session.gts +83 -0
  590. package/bundled-types/host-app/components/ai-assistant/panel-popover.gts +102 -0
  591. package/bundled-types/host-app/components/ai-assistant/panel.gts +453 -0
  592. package/bundled-types/host-app/components/ai-assistant/past-session-item.gts +323 -0
  593. package/bundled-types/host-app/components/ai-assistant/past-sessions.gts +115 -0
  594. package/bundled-types/host-app/components/ai-assistant/rename-session.gts +144 -0
  595. package/bundled-types/host-app/components/ai-assistant/restore-file-modal.gts +105 -0
  596. package/bundled-types/host-app/components/ai-assistant/skill-menu/index.gts +190 -0
  597. package/bundled-types/host-app/components/ai-assistant/skill-menu/skill-toggle.gts +168 -0
  598. package/bundled-types/host-app/components/ai-assistant/skill-menu/usage.gts +48 -0
  599. package/bundled-types/host-app/components/ai-assistant/toast.gts +293 -0
  600. package/bundled-types/host-app/components/card-catalog/modal.gts +579 -0
  601. package/bundled-types/host-app/components/card-error.gts +41 -0
  602. package/bundled-types/host-app/components/card-instance-picker/index.gts +54 -0
  603. package/bundled-types/host-app/components/card-pill.gts +189 -0
  604. package/bundled-types/host-app/components/card-prerender.gts +880 -0
  605. package/bundled-types/host-app/components/card-renderer.gts +84 -0
  606. package/bundled-types/host-app/components/card-search/constants.ts +76 -0
  607. package/bundled-types/host-app/components/card-search/item-button.gts +243 -0
  608. package/bundled-types/host-app/components/card-search/panel.gts +150 -0
  609. package/bundled-types/host-app/components/card-search/search-bar.gts +218 -0
  610. package/bundled-types/host-app/components/card-search/search-content.gts +604 -0
  611. package/bundled-types/host-app/components/card-search/search-result-header.gts +165 -0
  612. package/bundled-types/host-app/components/card-search/search-result-section.gts +545 -0
  613. package/bundled-types/host-app/components/card-search/section-header.gts +117 -0
  614. package/bundled-types/host-app/components/editor/directory.gts +308 -0
  615. package/bundled-types/host-app/components/editor/file-tree.gts +69 -0
  616. package/bundled-types/host-app/components/editor/import-module.gts +27 -0
  617. package/bundled-types/host-app/components/editor/indexed-file-tree.gts +572 -0
  618. package/bundled-types/host-app/components/editor/recent-files.gts +141 -0
  619. package/bundled-types/host-app/components/file-pill.gts +206 -0
  620. package/bundled-types/host-app/components/head-format-preview.gts +781 -0
  621. package/bundled-types/host-app/components/host-mode/breadcrumb-item.gts +150 -0
  622. package/bundled-types/host-app/components/host-mode/breadcrumbs.gts +131 -0
  623. package/bundled-types/host-app/components/host-mode/card.gts +199 -0
  624. package/bundled-types/host-app/components/host-mode/content.gts +195 -0
  625. package/bundled-types/host-app/components/host-mode/stack-item.gts +310 -0
  626. package/bundled-types/host-app/components/host-mode/stack.gts +91 -0
  627. package/bundled-types/host-app/components/matrix/auth-container.gts +66 -0
  628. package/bundled-types/host-app/components/matrix/auth.gts +73 -0
  629. package/bundled-types/host-app/components/matrix/forgot-password.gts +487 -0
  630. package/bundled-types/host-app/components/matrix/login.gts +241 -0
  631. package/bundled-types/host-app/components/matrix/register-user.gts +843 -0
  632. package/bundled-types/host-app/components/matrix/room-message-command.gts +330 -0
  633. package/bundled-types/host-app/components/matrix/room-message.gts +261 -0
  634. package/bundled-types/host-app/components/matrix/room.gts +1872 -0
  635. package/bundled-types/host-app/components/matrix/user-profile.gts +99 -0
  636. package/bundled-types/host-app/components/modal-container.gts +171 -0
  637. package/bundled-types/host-app/components/operator-mode/binary-file-info.gts +77 -0
  638. package/bundled-types/host-app/components/operator-mode/card-adoption-chain.gts +99 -0
  639. package/bundled-types/host-app/components/operator-mode/card-error-detail.gts +87 -0
  640. package/bundled-types/host-app/components/operator-mode/card-error.gts +177 -0
  641. package/bundled-types/host-app/components/operator-mode/card-schema-editor.gts +616 -0
  642. package/bundled-types/host-app/components/operator-mode/card-url-bar.gts +197 -0
  643. package/bundled-types/host-app/components/operator-mode/choose-file-modal.gts +562 -0
  644. package/bundled-types/host-app/components/operator-mode/choose-subscription-plan-modal.gts +517 -0
  645. package/bundled-types/host-app/components/operator-mode/code-editor.gts +736 -0
  646. package/bundled-types/host-app/components/operator-mode/code-submode/editor-indicator.gts +128 -0
  647. package/bundled-types/host-app/components/operator-mode/code-submode/format-chooser.gts +592 -0
  648. package/bundled-types/host-app/components/operator-mode/code-submode/inner-container.gts +162 -0
  649. package/bundled-types/host-app/components/operator-mode/code-submode/left-panel-toggle.gts +267 -0
  650. package/bundled-types/host-app/components/operator-mode/code-submode/module-inspector.gts +836 -0
  651. package/bundled-types/host-app/components/operator-mode/code-submode/playground/field-chooser-modal.gts +97 -0
  652. package/bundled-types/host-app/components/operator-mode/code-submode/playground/instance-chooser-dropdown.gts +346 -0
  653. package/bundled-types/host-app/components/operator-mode/code-submode/playground/playground-background.png +0 -0
  654. package/bundled-types/host-app/components/operator-mode/code-submode/playground/playground-panel.gts +1274 -0
  655. package/bundled-types/host-app/components/operator-mode/code-submode/playground/playground-preview.gts +155 -0
  656. package/bundled-types/host-app/components/operator-mode/code-submode/playground/playground.gts +98 -0
  657. package/bundled-types/host-app/components/operator-mode/code-submode/playground/spec-search.gts +70 -0
  658. package/bundled-types/host-app/components/operator-mode/code-submode/schema-editor.gts +205 -0
  659. package/bundled-types/host-app/components/operator-mode/code-submode/spec-preview-badge.gts +72 -0
  660. package/bundled-types/host-app/components/operator-mode/code-submode/spec-preview.gts +432 -0
  661. package/bundled-types/host-app/components/operator-mode/code-submode/toggle-button.gts +72 -0
  662. package/bundled-types/host-app/components/operator-mode/code-submode.gts +1030 -0
  663. package/bundled-types/host-app/components/operator-mode/container.gts +270 -0
  664. package/bundled-types/host-app/components/operator-mode/context-menu-button.gts +95 -0
  665. package/bundled-types/host-app/components/operator-mode/copy-button.gts +277 -0
  666. package/bundled-types/host-app/components/operator-mode/create-file-modal.gts +1087 -0
  667. package/bundled-types/host-app/components/operator-mode/create-listing-modal.gts +513 -0
  668. package/bundled-types/host-app/components/operator-mode/definition-container/base.gts +222 -0
  669. package/bundled-types/host-app/components/operator-mode/definition-container/clickable.gts +59 -0
  670. package/bundled-types/host-app/components/operator-mode/definition-container/divider.gts +48 -0
  671. package/bundled-types/host-app/components/operator-mode/definition-container/index.gts +108 -0
  672. package/bundled-types/host-app/components/operator-mode/delete-modal.gts +123 -0
  673. package/bundled-types/host-app/components/operator-mode/detail-panel-selector.gts +320 -0
  674. package/bundled-types/host-app/components/operator-mode/detail-panel.gts +834 -0
  675. package/bundled-types/host-app/components/operator-mode/edit-field-modal.gts +450 -0
  676. package/bundled-types/host-app/components/operator-mode/error-display.gts +590 -0
  677. package/bundled-types/host-app/components/operator-mode/host-submode/open-site-popover.gts +127 -0
  678. package/bundled-types/host-app/components/operator-mode/host-submode/publishing-realm-popover.gts +159 -0
  679. package/bundled-types/host-app/components/operator-mode/host-submode.gts +373 -0
  680. package/bundled-types/host-app/components/operator-mode/interact-submode/neighbor-stack-trigger.gts +116 -0
  681. package/bundled-types/host-app/components/operator-mode/interact-submode.gts +1021 -0
  682. package/bundled-types/host-app/components/operator-mode/new-file-button.gts +187 -0
  683. package/bundled-types/host-app/components/operator-mode/operator-mode-overlays.gts +511 -0
  684. package/bundled-types/host-app/components/operator-mode/overlays.gts +342 -0
  685. package/bundled-types/host-app/components/operator-mode/preview-panel/fitted-format-gallery.gts +127 -0
  686. package/bundled-types/host-app/components/operator-mode/preview-panel/index.gts +443 -0
  687. package/bundled-types/host-app/components/operator-mode/preview-panel/markdown-preview.gts +175 -0
  688. package/bundled-types/host-app/components/operator-mode/preview-panel/metadata-panel.gts +214 -0
  689. package/bundled-types/host-app/components/operator-mode/preview-panel/rendered-markdown.gts +718 -0
  690. package/bundled-types/host-app/components/operator-mode/private-dependency-violation.gts +70 -0
  691. package/bundled-types/host-app/components/operator-mode/profile/profile-email.gts +686 -0
  692. package/bundled-types/host-app/components/operator-mode/profile/profile-settings-modal.gts +454 -0
  693. package/bundled-types/host-app/components/operator-mode/profile/profile-subscription.gts +170 -0
  694. package/bundled-types/host-app/components/operator-mode/profile-info-popover.gts +263 -0
  695. package/bundled-types/host-app/components/operator-mode/publish-realm-modal.gts +1529 -0
  696. package/bundled-types/host-app/components/operator-mode/remove-field-modal.gts +135 -0
  697. package/bundled-types/host-app/components/operator-mode/send-error-to-ai-assistant.gts +195 -0
  698. package/bundled-types/host-app/components/operator-mode/stack-item.gts +1341 -0
  699. package/bundled-types/host-app/components/operator-mode/stack.gts +191 -0
  700. package/bundled-types/host-app/components/operator-mode/submode-layout.gts +781 -0
  701. package/bundled-types/host-app/components/operator-mode/syntax-error-display.gts +72 -0
  702. package/bundled-types/host-app/components/operator-mode/workspace-chooser/add-workspace.gts +282 -0
  703. package/bundled-types/host-app/components/operator-mode/workspace-chooser/index.gts +307 -0
  704. package/bundled-types/host-app/components/operator-mode/workspace-chooser/item-container.gts +50 -0
  705. package/bundled-types/host-app/components/operator-mode/workspace-chooser/workspace-loading-indicator.gts +50 -0
  706. package/bundled-types/host-app/components/operator-mode/workspace-chooser/workspace.gts +1092 -0
  707. package/bundled-types/host-app/components/pill-menu/index.gts +281 -0
  708. package/bundled-types/host-app/components/pill-menu/usage.gts +64 -0
  709. package/bundled-types/host-app/components/prerendered-card-search.gts +274 -0
  710. package/bundled-types/host-app/components/realm-dropdown.gts +209 -0
  711. package/bundled-types/host-app/components/realm-picker/index.gts +142 -0
  712. package/bundled-types/host-app/components/search-sheet/index.gts +426 -0
  713. package/bundled-types/host-app/components/search-sheet/usage.gts +99 -0
  714. package/bundled-types/host-app/components/submode-switcher.gts +260 -0
  715. package/bundled-types/host-app/components/type-picker/index.gts +111 -0
  716. package/bundled-types/host-app/components/with-known-realms-loaded.gts +47 -0
  717. package/bundled-types/host-app/components/with-loaded-realm.gts +39 -0
  718. package/bundled-types/host-app/components/with-subscription-data.gts +247 -0
  719. package/bundled-types/host-app/config/environment.ts +66 -0
  720. package/bundled-types/host-app/deprecation-workflow.js +30 -0
  721. package/bundled-types/host-app/lib/browser-queue.ts +198 -0
  722. package/bundled-types/host-app/lib/codemirror-context.ts +1081 -0
  723. package/bundled-types/host-app/lib/command-definitions.ts +41 -0
  724. package/bundled-types/host-app/lib/download-realm.ts +9 -0
  725. package/bundled-types/host-app/lib/example-card-helpers.ts +99 -0
  726. package/bundled-types/host-app/lib/externals.ts +247 -0
  727. package/bundled-types/host-app/lib/field-path-parser.ts +520 -0
  728. package/bundled-types/host-app/lib/file-def-manager.ts +712 -0
  729. package/bundled-types/host-app/lib/file-upload-state.ts +6 -0
  730. package/bundled-types/host-app/lib/formatted-message/utils.ts +272 -0
  731. package/bundled-types/host-app/lib/gc-card-store.ts +1084 -0
  732. package/bundled-types/host-app/lib/host-base-command.ts +29 -0
  733. package/bundled-types/host-app/lib/html-component.ts +144 -0
  734. package/bundled-types/host-app/lib/html-to-markdown.ts +96 -0
  735. package/bundled-types/host-app/lib/isolated-render.gts +90 -0
  736. package/bundled-types/host-app/lib/known-file-meta-urls.ts +17 -0
  737. package/bundled-types/host-app/lib/limited-set.ts +68 -0
  738. package/bundled-types/host-app/lib/matrix-classes/member.ts +28 -0
  739. package/bundled-types/host-app/lib/matrix-classes/message-builder.ts +444 -0
  740. package/bundled-types/host-app/lib/matrix-classes/message-code-patch-result.ts +44 -0
  741. package/bundled-types/host-app/lib/matrix-classes/message-command.ts +109 -0
  742. package/bundled-types/host-app/lib/matrix-classes/message.ts +239 -0
  743. package/bundled-types/host-app/lib/matrix-classes/room.ts +174 -0
  744. package/bundled-types/host-app/lib/matrix-utils.ts +114 -0
  745. package/bundled-types/host-app/lib/mutex.ts +62 -0
  746. package/bundled-types/host-app/lib/prerender-fetch-headers.ts +46 -0
  747. package/bundled-types/host-app/lib/prerender-util.ts +31 -0
  748. package/bundled-types/host-app/lib/public-path.ts +1 -0
  749. package/bundled-types/host-app/lib/random-name.ts +26 -0
  750. package/bundled-types/host-app/lib/realm-utils.ts +45 -0
  751. package/bundled-types/host-app/lib/search-cache-key.ts +33 -0
  752. package/bundled-types/host-app/lib/search-in-flight-key.ts +28 -0
  753. package/bundled-types/host-app/lib/search-replace-block-parsing.ts +101 -0
  754. package/bundled-types/host-app/lib/setup-globals.ts +15 -0
  755. package/bundled-types/host-app/lib/sqlite-adapter.ts +510 -0
  756. package/bundled-types/host-app/lib/stack-item.ts +164 -0
  757. package/bundled-types/host-app/lib/utils.ts +84 -0
  758. package/bundled-types/host-app/lib/window-error-handler.ts +132 -0
  759. package/bundled-types/host-app/router.ts +31 -0
  760. package/bundled-types/host-app/services/ai-assistant-panel-service.ts +850 -0
  761. package/bundled-types/host-app/services/billing-service.ts +208 -0
  762. package/bundled-types/host-app/services/card-service.ts +373 -0
  763. package/bundled-types/host-app/services/card-type-service.ts +226 -0
  764. package/bundled-types/host-app/services/code-semantics-service.ts +297 -0
  765. package/bundled-types/host-app/services/command-service.ts +975 -0
  766. package/bundled-types/host-app/services/environment-service.ts +26 -0
  767. package/bundled-types/host-app/services/error-display.ts +42 -0
  768. package/bundled-types/host-app/services/file-upload.ts +212 -0
  769. package/bundled-types/host-app/services/host-mode-service.ts +400 -0
  770. package/bundled-types/host-app/services/host-mode-state-service.ts +160 -0
  771. package/bundled-types/host-app/services/loader-service.ts +148 -0
  772. package/bundled-types/host-app/services/local-indexer.ts +68 -0
  773. package/bundled-types/host-app/services/local-persistence-service.ts +296 -0
  774. package/bundled-types/host-app/services/logger-service.ts +13 -0
  775. package/bundled-types/host-app/services/matrix-sdk-loader.ts +346 -0
  776. package/bundled-types/host-app/services/matrix-service.ts +2288 -0
  777. package/bundled-types/host-app/services/message-service.ts +84 -0
  778. package/bundled-types/host-app/services/module-contents-service.ts +333 -0
  779. package/bundled-types/host-app/services/monaco-service.ts +333 -0
  780. package/bundled-types/host-app/services/network.ts +101 -0
  781. package/bundled-types/host-app/services/operator-mode-state-service.ts +1593 -0
  782. package/bundled-types/host-app/services/playground-panel-service.ts +236 -0
  783. package/bundled-types/host-app/services/queue.ts +30 -0
  784. package/bundled-types/host-app/services/realm-info-service.ts +37 -0
  785. package/bundled-types/host-app/services/realm-server.ts +1211 -0
  786. package/bundled-types/host-app/services/realm.ts +1413 -0
  787. package/bundled-types/host-app/services/recent-cards-service.ts +174 -0
  788. package/bundled-types/host-app/services/recent-files-service.ts +224 -0
  789. package/bundled-types/host-app/services/render-error-state.ts +55 -0
  790. package/bundled-types/host-app/services/render-service.ts +362 -0
  791. package/bundled-types/host-app/services/render-store.ts +27 -0
  792. package/bundled-types/host-app/services/reset.ts +19 -0
  793. package/bundled-types/host-app/services/scroll-position-service.ts +81 -0
  794. package/bundled-types/host-app/services/spec-panel-service.ts +107 -0
  795. package/bundled-types/host-app/services/store.ts +2540 -0
  796. package/bundled-types/host-app/utils/assert-never.ts +3 -0
  797. package/bundled-types/host-app/utils/auth-error-guard.ts +183 -0
  798. package/bundled-types/host-app/utils/auth-service-worker-registration.ts +123 -0
  799. package/bundled-types/host-app/utils/card-search/query-builder.ts +169 -0
  800. package/bundled-types/host-app/utils/card-search/section-pagination.ts +57 -0
  801. package/bundled-types/host-app/utils/card-search/sections.ts +240 -0
  802. package/bundled-types/host-app/utils/card-search/type-filter.ts +124 -0
  803. package/bundled-types/host-app/utils/card-search/types.ts +11 -0
  804. package/bundled-types/host-app/utils/card-search/url.ts +27 -0
  805. package/bundled-types/host-app/utils/clipboard.ts +23 -0
  806. package/bundled-types/host-app/utils/editor/boxel-formatter.ts +65 -0
  807. package/bundled-types/host-app/utils/editor/editor-language.ts +88 -0
  808. package/bundled-types/host-app/utils/editor/monaco-test-waiter.ts +302 -0
  809. package/bundled-types/host-app/utils/file-def-attributes-extractor.ts +438 -0
  810. package/bundled-types/host-app/utils/file-name.ts +38 -0
  811. package/bundled-types/host-app/utils/id-from-card-or-url.ts +11 -0
  812. package/bundled-types/host-app/utils/lint-formatting.ts +57 -0
  813. package/bundled-types/host-app/utils/local-storage-keys.ts +28 -0
  814. package/bundled-types/host-app/utils/normalized-dir-path.ts +3 -0
  815. package/bundled-types/host-app/utils/prettify-messages.ts +21 -0
  816. package/bundled-types/host-app/utils/prettify-prompts.ts +21 -0
  817. package/bundled-types/host-app/utils/realm-index-boilerplate.ts +27 -0
  818. package/bundled-types/host-app/utils/register-boxel-transition.ts +17 -0
  819. package/bundled-types/host-app/utils/render-desync-detector.ts +362 -0
  820. package/bundled-types/host-app/utils/render-error.ts +455 -0
  821. package/bundled-types/host-app/utils/render-timer-stub.ts +249 -0
  822. package/bundled-types/host-app/utils/run-while-active.ts +14 -0
  823. package/bundled-types/host-app/utils/schema-editor.ts +25 -0
  824. package/bundled-types/host-app/utils/text-suggestion.ts +82 -0
  825. package/bundled-types/host-app/utils/titleize.ts +10 -0
  826. package/bundled-types/host-app/utils/uuid.ts +13 -0
  827. package/bundled-types/host-tests/helpers/adapter.ts +464 -0
  828. package/bundled-types/host-tests/helpers/base-realm.ts +464 -0
  829. package/bundled-types/host-tests/helpers/cards/view-card-demo.ts +119 -0
  830. package/bundled-types/host-tests/helpers/field-test-helpers.gts +54 -0
  831. package/bundled-types/host-tests/helpers/image-fixture.ts +3 -0
  832. package/bundled-types/host-tests/helpers/index.gts +2099 -0
  833. package/bundled-types/host-tests/helpers/indexer.ts +293 -0
  834. package/bundled-types/host-tests/helpers/interact-submode-setup.gts +551 -0
  835. package/bundled-types/host-tests/helpers/mock-matrix/_client.ts +1042 -0
  836. package/bundled-types/host-tests/helpers/mock-matrix/_sdk.ts +71 -0
  837. package/bundled-types/host-tests/helpers/mock-matrix/_server-state.ts +343 -0
  838. package/bundled-types/host-tests/helpers/mock-matrix/_sliding-sync.ts +140 -0
  839. package/bundled-types/host-tests/helpers/mock-matrix/_utils.ts +146 -0
  840. package/bundled-types/host-tests/helpers/mock-matrix.ts +200 -0
  841. package/bundled-types/host-tests/helpers/operator-mode-parameters-match.ts +71 -0
  842. package/bundled-types/host-tests/helpers/operator-mode-state.ts +18 -0
  843. package/bundled-types/host-tests/helpers/percy-snapshot.ts +228 -0
  844. package/bundled-types/host-tests/helpers/playground.ts +130 -0
  845. package/bundled-types/host-tests/helpers/realm-server-mock/index.ts +100 -0
  846. package/bundled-types/host-tests/helpers/realm-server-mock/routes.ts +617 -0
  847. package/bundled-types/host-tests/helpers/realm-server-mock/types.ts +24 -0
  848. package/bundled-types/host-tests/helpers/recent-files-cards.ts +86 -0
  849. package/bundled-types/host-tests/helpers/render-component.ts +40 -0
  850. package/bundled-types/host-tests/helpers/setup-qunit.js +164 -0
  851. package/bundled-types/host-tests/helpers/setup.ts +349 -0
  852. package/bundled-types/host-tests/helpers/shard-warmup.ts +72 -0
  853. package/bundled-types/host-tests/helpers/stream.ts +60 -0
  854. package/bundled-types/host-tests/helpers/test-auth.ts +27 -0
  855. package/bundled-types/host-tests/helpers/test-realm-registry.ts +23 -0
  856. package/bundled-types/host-tests/helpers/test-realm-service-worker.ts +92 -0
  857. package/bundled-types/host-tests/helpers/uncaught-exceptions.ts +16 -0
  858. package/bundled-types/host-tests/helpers/visit-operator-mode.ts +39 -0
  859. package/bundled-types/host-types/@cardstack/host/index.d.ts +3 -0
  860. package/bundled-types/host-types/@joplin/turndown-plugin-gfm/index.d.ts +15 -0
  861. package/bundled-types/host-types/@sqlite.org/sqlite-wasm/index.d.ts +122 -0
  862. package/bundled-types/host-types/ember-click-outside/modifiers/on-click-outside.ts +16 -0
  863. package/bundled-types/host-types/ember-concurrency/helpers/perform.d.ts +3 -0
  864. package/bundled-types/host-types/ember-css-url/index.d.ts +15 -0
  865. package/bundled-types/host-types/ember-data/types/registries/model.d.ts +6 -0
  866. package/bundled-types/host-types/ember-elsewhere/from-elsewhere.d.ts +12 -0
  867. package/bundled-types/host-types/ember-elsewhere/to-elsewhere.d.ts +16 -0
  868. package/bundled-types/host-types/ember-focus-trap/index.d.ts +4 -0
  869. package/bundled-types/host-types/ember-keyboard/modifiers/on-key.d.ts +15 -0
  870. package/bundled-types/host-types/index.d.ts +6 -0
  871. package/bundled-types/local-types/eslint-js.d.ts +5 -0
  872. package/bundled-types/local-types/index.d.ts +115 -0
  873. package/bundled-types/local-types/marked-gfm-heading-id.d.ts +12 -0
  874. package/bundled-types/local-types/matrix-js-sdk/index.d.ts +11 -0
  875. package/bundled-types/local-types/package.json +13 -0
  876. package/bundled-types/shims/boxel-cli-shims.d.ts +10 -0
  877. package/dist/index.js +100 -100
  878. package/package.json +13 -7
  879. package/src/commands/parse.ts +277 -34
  880. package/src/commands/realm/publish.ts +10 -1
  881. package/src/commands/realm/unpublish.ts +2 -3
  882. package/src/lib/describe-fetch-error.ts +25 -0
@@ -0,0 +1,2540 @@
1
+ import {
2
+ isDestroyed,
3
+ isDestroying,
4
+ registerDestructor,
5
+ } from '@ember/destroyable';
6
+ import type Owner from '@ember/owner';
7
+ import { getOwner } from '@ember/owner';
8
+ import Service, { service } from '@ember/service';
9
+ import { buildWaiter } from '@ember/test-waiters';
10
+
11
+ import { isTesting } from '@embroider/macros';
12
+
13
+ import { formatDistanceToNow } from 'date-fns';
14
+ import { task } from 'ember-concurrency';
15
+
16
+ import cloneDeep from 'lodash/cloneDeep';
17
+ import isEqual from 'lodash/isEqual';
18
+ import merge from 'lodash/merge';
19
+
20
+ import { TrackedObject, TrackedMap } from 'tracked-built-ins';
21
+
22
+ import {
23
+ baseFileRef,
24
+ CardError,
25
+ cardIdToURL,
26
+ isRegisteredPrefix,
27
+ hasExecutableExtension,
28
+ isCardError,
29
+ isCardInstance,
30
+ isFileDefInstance,
31
+ isFileMetaResource,
32
+ isSingleCardDocument,
33
+ isSingleFileMetaDocument,
34
+ isLinkableCollectionDocument,
35
+ resolveFileDefCodeRef,
36
+ X_BOXEL_JOB_PRIORITY_HEADER,
37
+ userInitiatedPriority,
38
+ Deferred,
39
+ delay,
40
+ mergeRelationships,
41
+ isLocalId,
42
+ realmURL as realmURLSymbol,
43
+ localId as localIdSymbol,
44
+ meta,
45
+ rri,
46
+ logger,
47
+ formattedError,
48
+ SupportedMimeType,
49
+ RealmPaths,
50
+ type Store as StoreInterface,
51
+ type AddOptions,
52
+ type CreateOptions,
53
+ type Query,
54
+ type DataQuery,
55
+ type QueryResultsMeta,
56
+ type RuntimeDependencyTrackingContext,
57
+ type PatchData,
58
+ type Relationship,
59
+ type AutoSaveState,
60
+ type CardDocument,
61
+ type SingleCardDocument,
62
+ type SingleFileMetaDocument,
63
+ type CardResourceMeta,
64
+ type LooseSingleCardDocument,
65
+ type LooseCardResource,
66
+ type CardErrorJSONAPI,
67
+ type CardErrorsJSONAPI,
68
+ type ErrorEntry,
69
+ type RenderError,
70
+ type FileMetaResource,
71
+ type LooseLinkableResource,
72
+ type LooseSingleResourceDocument,
73
+ type StoreReadType,
74
+ type CardResource,
75
+ type LinkableCollectionDocument,
76
+ type RealmIdentifier,
77
+ type RealmResourceIdentifier,
78
+ type Saved,
79
+ resolveCardReference,
80
+ } from '@cardstack/runtime-common';
81
+
82
+ import type { CardDef, BaseDef } from 'https://cardstack.com/base/card-api';
83
+ import type * as CardAPI from 'https://cardstack.com/base/card-api';
84
+ import type { FileDef } from 'https://cardstack.com/base/file-api';
85
+
86
+ import type { RealmEventContent } from 'https://cardstack.com/base/matrix-event';
87
+
88
+ import CardStore, { getDeps, type ReferenceCount } from '../lib/gc-card-store';
89
+
90
+ import {
91
+ consumingRealmHeader,
92
+ duringPrerenderHeaders,
93
+ jobIdHeader,
94
+ } from '../lib/prerender-fetch-headers';
95
+ import { searchCacheKey } from '../lib/search-cache-key';
96
+ import { searchInFlightKey } from '../lib/search-in-flight-key';
97
+ import { errorJsonApiToErrorEntry } from '../lib/window-error-handler';
98
+ import { getSearch } from '../resources/search';
99
+ import {
100
+ getSearchData,
101
+ type SearchDataResource,
102
+ } from '../resources/search-data';
103
+
104
+ import { FileDefAttributesExtractor } from '../utils/file-def-attributes-extractor';
105
+ import {
106
+ enableRenderTimerStub,
107
+ withTimersBlocked,
108
+ } from '../utils/render-timer-stub';
109
+
110
+ import type { CardSaveSubscriber } from './card-service';
111
+ import type CardService from './card-service';
112
+ import type CommandService from './command-service';
113
+ import type EnvironmentService from './environment-service';
114
+
115
+ import type HostModeService from './host-mode-service';
116
+ import type LoaderService from './loader-service';
117
+ import type MessageService from './message-service';
118
+ import type NetworkService from './network';
119
+ import type OperatorModeStateService from './operator-mode-state-service';
120
+ import type RealmService from './realm';
121
+ import type RealmServerService from './realm-server';
122
+ import type ResetService from './reset';
123
+ import type { SearchResource } from '../resources/search';
124
+
125
+ export { CardErrorJSONAPI, CardSaveSubscriber };
126
+
127
+ let waiter = buildWaiter('store-service');
128
+
129
+ const realmEventsLogger = logger('realm:events');
130
+ const storeLogger = logger('store');
131
+
132
+ // Companion to `jobIdHeader()` (re-exported from
133
+ // `../lib/prerender-fetch-headers`). Policy is two-state, gated by
134
+ // `__boxelDuringPrerender`, not by the presence of
135
+ // `__boxelJobPriority`:
136
+ //
137
+ // 1. Inside a prerender tab: forward the worker job's priority as-is.
138
+ // The render-runner injects `__boxelJobPriority` alongside
139
+ // `__boxelJobId` on each visit — a priority of 0 is meaningful
140
+ // (the originating job is system-initiated background indexing)
141
+ // and must be preserved, not upgraded. Sub-`prerenderModule`
142
+ // calls fired by `_federated-search` for a `lookupDefinition`
143
+ // cache miss inherit this priority so they don't outrun the
144
+ // parent. If `__boxelJobPriority` is missing here (older
145
+ // render-runner build, test fixture, etc.) treat as 0 — the
146
+ // safe default for prerender-context work.
147
+ //
148
+ // 2. Outside a prerender tab (the host SPA in a real user's browser):
149
+ // stamp `userInitiatedPriority` (10). User clicks driving a
150
+ // search are by definition user-initiated work and should outrank
151
+ // background indexing on the realm-server's PagePool. Without
152
+ // this, a user search whose definition lookup misses the modules
153
+ // cache would fire its sub-prerender at priority 0 and queue
154
+ // behind concurrent indexing fan-out.
155
+ //
156
+ // External (non-host) HTTP callers — anything that doesn't run in
157
+ // the host SPA's JS runtime — bypass this helper entirely and set
158
+ // `X-Boxel-Job-Priority` directly on their request if they care.
159
+ // This helper covers the host SPA only.
160
+ //
161
+ // Both globals are checked with `=== true` / strict-number rather
162
+ // than truthy coercion: `__boxelDuringPrerender` is typed as a
163
+ // boolean and a stray truthy string from a future code path
164
+ // shouldn't silently flip the policy from "user-priority" to
165
+ // "preserve 0."
166
+ // Pure resolver — exported for the unit test in
167
+ // `tests/integration/job-priority-header-test.ts`. See the comment
168
+ // above for the policy rationale; the function is the literal
169
+ // translation of that policy to numbers.
170
+ export function resolveOutboundJobPriority({
171
+ duringPrerender,
172
+ jobPriority,
173
+ }: {
174
+ duringPrerender: unknown;
175
+ jobPriority: unknown;
176
+ }): number {
177
+ let valid =
178
+ typeof jobPriority === 'number' &&
179
+ Number.isSafeInteger(jobPriority) &&
180
+ jobPriority >= 0
181
+ ? jobPriority
182
+ : undefined;
183
+ if (duringPrerender === true) {
184
+ return valid ?? 0;
185
+ }
186
+ return valid ?? userInitiatedPriority;
187
+ }
188
+
189
+ function jobPriorityHeader(): Record<string, string> {
190
+ let g = globalThis as unknown as {
191
+ __boxelDuringPrerender?: boolean;
192
+ __boxelJobPriority?: number;
193
+ };
194
+ return {
195
+ [X_BOXEL_JOB_PRIORITY_HEADER]: String(
196
+ resolveOutboundJobPriority({
197
+ duringPrerender: g.__boxelDuringPrerender,
198
+ jobPriority: g.__boxelJobPriority,
199
+ }),
200
+ ),
201
+ };
202
+ }
203
+ const queryFieldSeedFromSearchSymbol = Symbol.for(
204
+ 'cardstack-query-field-seed-from-search',
205
+ );
206
+
207
+ type PersistOptions = CreateOptions & { clientRequestId?: string };
208
+ type DependencyTrackingOptions = {
209
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
210
+ };
211
+ type TrackedCreateOptions = CreateOptions & DependencyTrackingOptions;
212
+ type TrackedAddOptions = AddOptions & DependencyTrackingOptions;
213
+
214
+ export default class StoreService extends Service implements StoreInterface {
215
+ @service declare private realm: RealmService;
216
+ @service declare private loaderService: LoaderService;
217
+ @service declare private messageService: MessageService;
218
+ @service declare private cardService: CardService;
219
+ @service declare private commandService: CommandService;
220
+ @service declare private hostModeService: HostModeService;
221
+ @service declare private network: NetworkService;
222
+ @service declare private environmentService: EnvironmentService;
223
+ @service declare private reset: ResetService;
224
+ @service declare private operatorModeStateService: OperatorModeStateService;
225
+ @service declare private realmServer: RealmServerService;
226
+ private subscriptions: Map<string, { unsubscribe: () => void }> = new Map();
227
+ private referenceCount: ReferenceCount = new Map();
228
+ private newReferencePromises: Promise<void>[] = [];
229
+ private autoSaveStates: TrackedMap<string, AutoSaveState> = new TrackedMap();
230
+ private cardApiCache?: typeof CardAPI;
231
+ private gcInterval: number | undefined;
232
+ private ready: Promise<void>;
233
+ private inflightGetCards: Map<string, Promise<CardDef | CardErrorJSONAPI>> =
234
+ new Map();
235
+ private inflightGetFileMeta: Map<
236
+ string,
237
+ Promise<FileDef | CardErrorJSONAPI>
238
+ > = new Map();
239
+ private inflightCardMutations: Map<string, Promise<void>> = new Map();
240
+ private inflightCardLoads: Map<string, Deferred<void>> = new Map();
241
+ // Coalesce concurrent same-(realms, query) `_federated-search` HTTP
242
+ // calls during a prerender. Mirrors
243
+ // `RealmIndexQueryEngine.#inFlightSearch` server-side. Gated on
244
+ // `__boxelRenderContext` so live user searches stay uncoalesced —
245
+ // write-then-read freshness story unchanged outside prerender.
246
+ // Entries self-clear on `.finally()` via identity check.
247
+ private inflightSearch: Map<string, Promise<LinkableCollectionDocument>> =
248
+ new Map();
249
+ // Resolved-doc cache for same-realm `_federated-search` calls during
250
+ // a prerender. Layered *above* `inflightSearch`: a cache hit skips
251
+ // the network round-trip entirely; a miss falls through to the
252
+ // in-flight Map and the cache is populated on resolve. Keyed by
253
+ // (jobId, consumingRealm, query) — gated to same-realm-only so a
254
+ // cross-realm read can't freeze a value while a peer realm-server
255
+ // replica swaps mid-job.
256
+ //
257
+ // Lifetime: the entire indexing job. One job typically spans many
258
+ // card renders in the same prerender tab (each navigation activates
259
+ // and deactivates the render route but all those visits share one
260
+ // `__boxelJobId`); the cache must survive those route bounces so
261
+ // earlier renders' work is reusable by later ones. Only clear when
262
+ // the job actually changes — `fetchSearchDoc` does this at
263
+ // fetch-entry via the jobId-change check, and `resetState` /
264
+ // `resetCache` do it on harder service resets. The render route's
265
+ // `deactivate` deliberately does NOT clear this cache. See
266
+ // `search-cache-key.ts` for the digest and the realm-server's
267
+ // `job-scoped-search-cache.ts` for the server-side prior art on
268
+ // storing resolved docs rather than promises (avoids tail-latency
269
+ // stalls on slow first populate).
270
+ private searchCache: Map<string, LinkableCollectionDocument> = new Map();
271
+ // The jobId the `searchCache` entries belong to. When a request
272
+ // arrives carrying a different `__boxelJobId` we drop the cache
273
+ // before serving — belt-and-braces beside `resetState()` and the
274
+ // render-route deactivate clear, in case a prerender tab is reused
275
+ // across jobs without driving either of those paths.
276
+ private searchCacheJobId: string | undefined = undefined;
277
+ // Monotonic counter bumped on every clear of `searchCache` (every
278
+ // path that empties the map: `clearSearchCache`, `resetState`,
279
+ // `resetCache`, the jobId-change clear at fetch-entry). A
280
+ // `fetchSearchDoc` call captures this at entry and checks it before
281
+ // populating on resolve — if the cache was intentionally cleared
282
+ // while the request was in flight, the resolved doc must not
283
+ // repopulate against the new generation. Mirrors the identity
284
+ // check on the in-flight Map but for the resolved-doc layer where
285
+ // we can't compare against a stored Promise.
286
+ private searchCacheGeneration = 0;
287
+ private store: CardStore;
288
+ protected isRenderStore = false;
289
+
290
+ // This is used for tests
291
+ private onSaveSubscriber: CardSaveSubscriber | undefined;
292
+ private autoSaveQueues = new Map<string, { isImmediate?: true }[]>();
293
+ private autoSavePromises = new Map<string, Promise<void>>();
294
+
295
+ constructor(owner: Owner) {
296
+ super(owner);
297
+ this.store = this.createCardStore();
298
+ this.reset.register(this);
299
+ this.ready = this.setup();
300
+ registerDestructor(this, () => {
301
+ clearInterval(this.gcInterval);
302
+ });
303
+ }
304
+
305
+ protected renderContextBlocksPersistence() {
306
+ return (
307
+ this.isRenderStore && Boolean((globalThis as any).__boxelRenderContext)
308
+ );
309
+ }
310
+
311
+ // used for tests only!
312
+ _onSave(subscriber: CardSaveSubscriber) {
313
+ this.onSaveSubscriber = subscriber;
314
+ this.cardService._onSave(subscriber);
315
+ }
316
+
317
+ // used for tests only!
318
+ _unregisterSaveSubscriber() {
319
+ this.onSaveSubscriber = undefined;
320
+ this.cardService._unregisterSaveSubscriber();
321
+ }
322
+
323
+ resetState() {
324
+ clearInterval(this.gcInterval);
325
+ this.subscriptions = new Map();
326
+ this.onSaveSubscriber = undefined;
327
+ this.referenceCount = new Map();
328
+ this.newReferencePromises = [];
329
+ this.autoSaveStates = new TrackedMap();
330
+ this.inflightGetCards = new Map();
331
+ this.inflightGetFileMeta = new Map();
332
+ this.inflightCardMutations = new Map();
333
+ this.inflightCardLoads = new Map();
334
+ this.inflightSearch = new Map();
335
+ this.searchCache = new Map();
336
+ this.searchCacheJobId = undefined;
337
+ this.searchCacheGeneration++;
338
+ this.autoSaveQueues = new Map();
339
+ this.autoSavePromises = new Map();
340
+ this.store = this.createCardStore();
341
+ this.ready = this.setup();
342
+ }
343
+
344
+ async ensureSetupComplete(): Promise<void> {
345
+ await this.ready;
346
+ }
347
+
348
+ // Drop every pending in-flight search entry. Callers awaiting an
349
+ // existing promise still get their answer (the underlying HTTP is
350
+ // already in motion); only *new* same-key callers after the drop
351
+ // miss the map and re-fetch. Wire this to anything the host
352
+ // recognizes as an invalidation boundary — render-route deactivate
353
+ // is the obvious one inside a prerender tab.
354
+ clearInFlightSearch(): void {
355
+ this.inflightSearch.clear();
356
+ }
357
+
358
+ // Drop every resolved-doc search-cache entry. Used for hard resets
359
+ // (`resetState`, `resetCache`) and by tests; NOT called from the
360
+ // render route's per-visit deactivate, because the cache is meant
361
+ // to survive across renders within a single indexing job. Cross-job
362
+ // invalidation is handled by `fetchSearchDoc`'s entry-time
363
+ // jobId-change clear, which fires the first time a new
364
+ // `__boxelJobId` is observed.
365
+ clearSearchCache(): void {
366
+ this.searchCache.clear();
367
+ this.searchCacheJobId = undefined;
368
+ this.searchCacheGeneration++;
369
+ }
370
+
371
+ resetCache(opts?: { preserveReferences?: boolean }) {
372
+ storeLogger.debug('resetting store cache');
373
+ if (!opts?.preserveReferences) {
374
+ this.referenceCount = new Map();
375
+ }
376
+ this.cardApiCache = undefined;
377
+ this.autoSaveStates = new TrackedMap();
378
+ this.newReferencePromises = [];
379
+ this.inflightGetCards = new Map();
380
+ this.inflightGetFileMeta = new Map();
381
+ this.inflightCardMutations = new Map();
382
+ this.inflightCardLoads = new Map();
383
+ this.inflightSearch = new Map();
384
+ this.searchCache = new Map();
385
+ this.searchCacheJobId = undefined;
386
+ this.searchCacheGeneration++;
387
+ this.autoSaveQueues = new Map();
388
+ this.autoSavePromises = new Map();
389
+ this.store = this.createCardStore();
390
+ }
391
+
392
+ refreshReferencesForCodeChange(reason?: string) {
393
+ let reasonSuffix = reason ? ` (${reason})` : '';
394
+ storeLogger.debug(`resetting store for code change${reasonSuffix}`);
395
+ this.store.reset();
396
+ this.reestablishReferences.perform();
397
+ }
398
+
399
+ dropReference(id: string | undefined) {
400
+ if (!id) {
401
+ return;
402
+ }
403
+ id = asURL(id);
404
+ let currentReferenceCount = this.referenceCount.get(id) ?? 0;
405
+ currentReferenceCount -= 1;
406
+ this.referenceCount.set(id, currentReferenceCount);
407
+
408
+ storeLogger.debug(
409
+ `dropping reference to ${id}, current reference count: ${this.referenceCount.get(id)}`,
410
+ );
411
+ if (currentReferenceCount <= 0) {
412
+ if (currentReferenceCount < 0) {
413
+ let message = `current reference count for ${id} is negative: ${this.referenceCount.get(id)}`;
414
+ storeLogger.error(message);
415
+ console.trace(message); // this will helps us to understand who dropped the reference that made it negative
416
+ }
417
+ this.referenceCount.delete(id);
418
+ this.autoSaveStates.delete(id);
419
+ this.unsubscribeFromInstance(id);
420
+ }
421
+ }
422
+
423
+ addReference(id: string | undefined, opts?: { type?: StoreReadType }) {
424
+ if (!id) {
425
+ return;
426
+ }
427
+ id = asURL(id);
428
+ let readType: StoreReadType = opts?.type ?? 'card';
429
+ // synchronously update the reference count so we don't run into race
430
+ // conditions requiring a mutex
431
+ let currentReferenceCount = this.referenceCount.get(id) ?? 0;
432
+ currentReferenceCount += 1;
433
+ this.referenceCount.set(id, currentReferenceCount);
434
+ storeLogger.debug(
435
+ `adding reference to ${id}, current reference count: ${this.referenceCount.get(id)}`,
436
+ );
437
+
438
+ if (isLocalId(id)) {
439
+ let instanceOrError = this.peek(id);
440
+ if (instanceOrError) {
441
+ let realmURL = isCardInstance(instanceOrError)
442
+ ? instanceOrError[realmURLSymbol]?.href
443
+ : instanceOrError.realm;
444
+ if (realmURL) {
445
+ this.subscribeToRealm(new URL(realmURL));
446
+ }
447
+ }
448
+ } else {
449
+ this.subscribeToRealm(rri(id));
450
+ // intentionally not awaiting this. we keep track of the promise in
451
+ // this.newReferencePromises
452
+ this.wireUpNewReference(id, readType);
453
+ }
454
+ }
455
+
456
+ loaded(): Promise<void> {
457
+ return this.store.loaded();
458
+ }
459
+
460
+ get loadGeneration(): number {
461
+ return this.store.loadGeneration;
462
+ }
463
+
464
+ trackLoad(load: Promise<unknown>): void {
465
+ this.store.trackLoad(load);
466
+ }
467
+
468
+ // CS-10872: pass-through so SearchResource / other callers can tag
469
+ // their load promises with the metadata we want to see in a timeout
470
+ // error document ("what query fields were still pending").
471
+ trackQueryLoad(
472
+ load: Promise<unknown>,
473
+ meta: import('https://cardstack.com/base/card-api').QueryLoadMeta,
474
+ ): (() => void) | void {
475
+ return (
476
+ this.store as unknown as {
477
+ trackQueryLoad?: (
478
+ l: Promise<unknown>,
479
+ m: import('https://cardstack.com/base/card-api').QueryLoadMeta,
480
+ ) => (() => void) | void;
481
+ }
482
+ ).trackQueryLoad?.(load, meta);
483
+ }
484
+
485
+ queryLoadsInFlight(): import('https://cardstack.com/base/card-api').QueryLoadInfo[] {
486
+ return (
487
+ (
488
+ this.store as unknown as {
489
+ queryLoadsInFlight?: () => import('https://cardstack.com/base/card-api').QueryLoadInfo[];
490
+ }
491
+ ).queryLoadsInFlight?.() ?? []
492
+ );
493
+ }
494
+
495
+ // CS-10872: pass-throughs for the per-item diagnostic accessors.
496
+ // Each returns [] when the underlying store doesn't implement the
497
+ // hook (older test doubles, in-memory stores in node-side tests).
498
+ cardDocLoadsInFlight(): Array<{ url: string; ageMs: number }> {
499
+ return (
500
+ (
501
+ this.store as unknown as {
502
+ cardDocLoadsInFlight?: () => Array<{ url: string; ageMs: number }>;
503
+ }
504
+ ).cardDocLoadsInFlight?.() ?? []
505
+ );
506
+ }
507
+ fileMetaDocLoadsInFlight(): Array<{ url: string; ageMs: number }> {
508
+ return (
509
+ (
510
+ this.store as unknown as {
511
+ fileMetaDocLoadsInFlight?: () => Array<{
512
+ url: string;
513
+ ageMs: number;
514
+ }>;
515
+ }
516
+ ).fileMetaDocLoadsInFlight?.() ?? []
517
+ );
518
+ }
519
+ recentCardDocLoads(): Array<{ url: string; ms: number }> {
520
+ return (
521
+ (
522
+ this.store as unknown as {
523
+ recentCardDocLoads?: () => Array<{ url: string; ms: number }>;
524
+ }
525
+ ).recentCardDocLoads?.() ?? []
526
+ );
527
+ }
528
+ recentFileMetaLoads(): Array<{ url: string; ms: number }> {
529
+ return (
530
+ (
531
+ this.store as unknown as {
532
+ recentFileMetaLoads?: () => Array<{ url: string; ms: number }>;
533
+ }
534
+ ).recentFileMetaLoads?.() ?? []
535
+ );
536
+ }
537
+ recentQueryLoads(): Array<{
538
+ meta: import('https://cardstack.com/base/card-api').QueryLoadMeta;
539
+ ms: number;
540
+ }> {
541
+ return (
542
+ (
543
+ this.store as unknown as {
544
+ recentQueryLoads?: () => Array<{
545
+ meta: import('https://cardstack.com/base/card-api').QueryLoadMeta;
546
+ ms: number;
547
+ }>;
548
+ }
549
+ ).recentQueryLoads?.() ?? []
550
+ );
551
+ }
552
+
553
+ get cardDocsInFlight() {
554
+ return this.store.cardDocsInFlight;
555
+ }
556
+
557
+ get fileMetaDocsInFlight() {
558
+ return this.store.fileMetaDocsInFlight;
559
+ }
560
+
561
+ // This method creates a new instance in the store and return the new card ID
562
+ async create(
563
+ doc: LooseSingleCardDocument,
564
+ opts?: TrackedCreateOptions,
565
+ ): Promise<string | CardErrorJSONAPI> {
566
+ return await this.withTestWaiters(async () => {
567
+ if (opts?.realm) {
568
+ doc.data.meta = {
569
+ ...(doc.data.meta ?? {}),
570
+ realmURL: opts.realm as RealmIdentifier,
571
+ };
572
+ }
573
+ let cardOrError = await this.getCardInstance({
574
+ idOrDoc: doc,
575
+ relativeTo: opts?.relativeTo,
576
+ realm: opts?.realm,
577
+ opts: {
578
+ localDir: opts?.localDir,
579
+ dependencyTrackingContext: opts?.dependencyTrackingContext,
580
+ },
581
+ });
582
+ if (isCardInstance(cardOrError)) {
583
+ return cardOrError.id;
584
+ }
585
+ return cardOrError;
586
+ });
587
+ }
588
+
589
+ save(id: string) {
590
+ this.doAutoSave(id, { isImmediate: true });
591
+ }
592
+
593
+ async add<T extends CardDef>(
594
+ instanceOrDoc: T | LooseSingleCardDocument,
595
+ opts?: TrackedCreateOptions & { doNotPersist: true },
596
+ ): Promise<T>;
597
+ async add<T extends CardDef>(
598
+ instanceOrDoc: T | LooseSingleCardDocument,
599
+ opts?: TrackedCreateOptions & { doNotWaitForPersist: true },
600
+ ): Promise<T>;
601
+ async add<T extends CardDef>(
602
+ instanceOrDoc: T | LooseSingleCardDocument,
603
+ opts?: TrackedCreateOptions,
604
+ ): Promise<T | CardErrorJSONAPI>;
605
+ async add<T extends CardDef>(
606
+ instanceOrDoc: T | LooseSingleCardDocument,
607
+ opts?: TrackedAddOptions,
608
+ ): Promise<T | CardErrorJSONAPI> {
609
+ let instance: T;
610
+ if (!isCardInstance(instanceOrDoc)) {
611
+ instance = await this.createFromSerialized(
612
+ instanceOrDoc.data,
613
+ instanceOrDoc,
614
+ opts?.relativeTo,
615
+ opts?.dependencyTrackingContext,
616
+ );
617
+ } else {
618
+ instance = instanceOrDoc;
619
+ let api = await this.cardService.getAPI();
620
+ let deps = getDeps(api, instance);
621
+ for (let dep of deps) {
622
+ if (isCardInstance(dep)) {
623
+ if (!this.store.getCard(dep[localIdSymbol])) {
624
+ this.store.setCard(dep.id ?? dep[localIdSymbol], dep);
625
+ }
626
+ continue;
627
+ }
628
+ if (isFileDefInstance(dep) && dep.id) {
629
+ if (!this.store.getFileMeta(dep.id)) {
630
+ this.store.setFileMeta(dep.id, dep);
631
+ }
632
+ }
633
+ }
634
+ }
635
+ if (opts?.realm) {
636
+ instance[meta] = {
637
+ ...instance[meta],
638
+ ...{ realmURL: opts.realm },
639
+ } as CardResourceMeta;
640
+ }
641
+
642
+ let maybeOldInstance = instance.id
643
+ ? this.store.getCard(instance.id)
644
+ : undefined;
645
+ if (maybeOldInstance) {
646
+ await this.stopAutoSaving(maybeOldInstance);
647
+ }
648
+
649
+ this.setIdentityContext(instance);
650
+ await this.startAutoSaving(instance);
651
+
652
+ if (this.renderContextBlocksPersistence()) {
653
+ return instance;
654
+ }
655
+
656
+ if (opts?.doNotWaitForPersist) {
657
+ // intentionally not awaiting
658
+ this.persistAndUpdate(instance, {
659
+ realm: opts?.realm,
660
+ localDir: opts?.localDir,
661
+ });
662
+ } else if (!opts?.doNotPersist) {
663
+ if (instance.id) {
664
+ this.save(instance.id);
665
+ } else {
666
+ return (await this.persistAndUpdate(instance, {
667
+ realm: opts?.realm,
668
+ localDir: opts?.localDir,
669
+ })) as T | CardErrorJSONAPI;
670
+ }
671
+ }
672
+
673
+ return instance;
674
+ }
675
+
676
+ // peek will return a stale instance in the case the server has an error for
677
+ // this id
678
+ peek<T extends CardDef>(
679
+ id: string,
680
+ opts?: { type?: 'card' },
681
+ ): T | CardErrorJSONAPI | undefined;
682
+ peek<T extends FileDef>(
683
+ id: string,
684
+ opts: { type: 'file-meta' },
685
+ ): T | CardErrorJSONAPI | undefined;
686
+ peek<T extends CardDef | FileDef>(
687
+ id: string,
688
+ opts?: { type?: StoreReadType },
689
+ ): T | CardErrorJSONAPI | undefined {
690
+ id = asURL(id);
691
+ let readType = opts?.type ?? 'card';
692
+ if (readType === 'file-meta') {
693
+ return this.store.getFileMetaInstanceOrError<T & FileDef>(id);
694
+ }
695
+ return this.store.getCardInstanceOrError<T & CardDef>(id);
696
+ }
697
+
698
+ // peekError will always return the current server state regarding errors for this id
699
+ peekError(id: string, opts?: { type?: 'card' }): CardErrorJSONAPI | undefined;
700
+ peekError(
701
+ id: string,
702
+ opts: { type: 'file-meta' },
703
+ ): CardErrorJSONAPI | undefined;
704
+ peekError(
705
+ id: string,
706
+ opts?: { type?: StoreReadType },
707
+ ): CardErrorJSONAPI | undefined {
708
+ id = asURL(id);
709
+ let readType = opts?.type ?? 'card';
710
+ if (readType === 'file-meta') {
711
+ return this.store.getFileMetaError(id);
712
+ }
713
+ return this.store.getCardError(id);
714
+ }
715
+
716
+ async get<T extends CardDef>(
717
+ id: string,
718
+ opts?: {
719
+ type?: 'card';
720
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
721
+ },
722
+ ): Promise<T | CardErrorJSONAPI>;
723
+ async get<T extends FileDef>(
724
+ id: string,
725
+ opts: {
726
+ type: 'file-meta';
727
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
728
+ },
729
+ ): Promise<T | CardErrorJSONAPI>;
730
+ async get<T extends CardDef | FileDef>(
731
+ id: string,
732
+ opts?: {
733
+ type?: StoreReadType;
734
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
735
+ },
736
+ ): Promise<T | CardErrorJSONAPI> {
737
+ let readType = opts?.type ?? 'card';
738
+ if (readType === 'file-meta') {
739
+ return await this.getFileMetaInstance<T & FileDef>({
740
+ idOrDoc: id,
741
+ opts: { dependencyTrackingContext: opts?.dependencyTrackingContext },
742
+ });
743
+ }
744
+ return await this.getCardInstance<T & CardDef>({
745
+ idOrDoc: id,
746
+ opts: { dependencyTrackingContext: opts?.dependencyTrackingContext },
747
+ });
748
+ }
749
+
750
+ // Bypass cached state and fetch from source of truth
751
+ async getWithoutCache<T extends CardDef>(
752
+ id: string,
753
+ opts?: { type?: 'card' },
754
+ ): Promise<T | CardErrorJSONAPI>;
755
+ async getWithoutCache<T extends FileDef>(
756
+ id: string,
757
+ opts: { type: 'file-meta' },
758
+ ): Promise<T | CardErrorJSONAPI>;
759
+ async getWithoutCache<T extends CardDef | FileDef>(
760
+ id: string,
761
+ opts?: { type?: StoreReadType },
762
+ ): Promise<T | CardErrorJSONAPI> {
763
+ let readType = opts?.type ?? 'card';
764
+ if (readType === 'file-meta') {
765
+ return await this.getFileMetaInstance<T & FileDef>({
766
+ idOrDoc: id,
767
+ opts: { noCache: true },
768
+ });
769
+ }
770
+ return await this.getCardInstance<T & CardDef>({
771
+ idOrDoc: id,
772
+ opts: { noCache: true },
773
+ });
774
+ }
775
+
776
+ async serializeFileDefAsDocument(
777
+ fileDef: FileDef,
778
+ ): Promise<SingleFileMetaDocument> {
779
+ let api = await this.cardService.getAPI();
780
+ return api.serializeFileDef(fileDef) as SingleFileMetaDocument;
781
+ }
782
+
783
+ async delete(id: string): Promise<void> {
784
+ id = asURL(id);
785
+ if (!id) {
786
+ // the card isn't actually saved yet, so do nothing
787
+ return;
788
+ }
789
+ this.unsubscribeFromInstance(id);
790
+ this.store.delete(id);
791
+ await this.cardService.fetchJSON(id, { method: 'DELETE' });
792
+ }
793
+
794
+ async patch<T extends CardDef = CardDef>(
795
+ id: string,
796
+ patch: PatchData,
797
+ opts?: { doNotPersist?: true },
798
+ ): Promise<T | CardErrorJSONAPI | undefined>;
799
+ async patch<T extends CardDef = CardDef>(
800
+ id: string,
801
+ patch: PatchData,
802
+ opts?: { doNotWaitForPersist?: true },
803
+ ): Promise<T | CardErrorJSONAPI | undefined>;
804
+ async patch<T extends CardDef = CardDef>(
805
+ id: string,
806
+ patch: PatchData,
807
+ opts?: { doNotPersist?: true; doNotWaitForPersist?: true },
808
+ ): Promise<T | CardErrorJSONAPI | undefined>;
809
+ async patch<T extends CardDef = CardDef>(
810
+ id: string,
811
+ patch: PatchData,
812
+ opts?: { clientRequestId?: string },
813
+ ): Promise<T | CardErrorJSONAPI | undefined>;
814
+ async patch<T extends CardDef = CardDef>(
815
+ id: string,
816
+ patch: PatchData,
817
+ opts?: { doNotWaitForPersist?: true; clientRequestId?: string },
818
+ ): Promise<T | CardErrorJSONAPI | undefined>;
819
+ async patch<T extends CardDef = CardDef>(
820
+ id: string,
821
+ patch: PatchData,
822
+ opts?: {
823
+ doNotPersist?: true;
824
+ doNotWaitForPersist?: true;
825
+ clientRequestId?: string;
826
+ },
827
+ ): Promise<T | CardErrorJSONAPI | undefined> {
828
+ if (this.renderContextBlocksPersistence()) {
829
+ return;
830
+ }
831
+ // eslint-disable-next-line ember/classic-decorator-no-classic-methods
832
+ let instance = await this.get<T>(id);
833
+ if (!instance || !isCardInstance(instance)) {
834
+ return;
835
+ }
836
+ if (opts?.doNotPersist) {
837
+ await this.stopAutoSaving(instance);
838
+ }
839
+ let doc = await this.cardService.serializeCard(instance, {
840
+ omitQueryFields: true,
841
+ });
842
+ if (patch.attributes) {
843
+ doc.data.attributes = merge(doc.data.attributes, patch.attributes);
844
+ }
845
+ if (patch.relationships) {
846
+ let mergedRel = mergeRelationships(
847
+ doc.data.relationships,
848
+ patch.relationships,
849
+ );
850
+ if (mergedRel && Object.keys(mergedRel).length !== 0) {
851
+ doc.data.relationships = mergedRel;
852
+ }
853
+ }
854
+ if (patch.meta) {
855
+ doc.data.meta = merge(doc.data.meta, patch.meta);
856
+ }
857
+ let linkedCards = await this.loadPatchedInstances(
858
+ patch,
859
+ instance.id ? cardIdToURL(instance.id) : undefined,
860
+ );
861
+ for (let [field, value] of Object.entries(linkedCards)) {
862
+ if (field.includes('.')) {
863
+ let parts = field.split('.');
864
+ let leaf = parts.pop();
865
+ if (!leaf) {
866
+ throw new Error(`bug: error in field name "${field}"`);
867
+ }
868
+ let inner = instance;
869
+ for (let part of parts) {
870
+ inner = (inner as any)[part];
871
+ }
872
+ (inner as any)[leaf.match(/^\d+$/) ? Number(leaf) : leaf] = value;
873
+ } else {
874
+ (instance as any)[field] = value;
875
+ }
876
+ }
877
+ let api = await this.cardService.getAPI();
878
+ await api.updateFromSerialized(instance, doc, this.store);
879
+ let shouldPersist = !opts?.doNotPersist;
880
+ let shouldAwaitPersist = shouldPersist && !opts?.doNotWaitForPersist;
881
+ let persistedResult: CardDef | CardErrorJSONAPI | undefined = instance;
882
+
883
+ if (opts?.doNotPersist) {
884
+ await this.startAutoSaving(instance);
885
+ } else if (shouldPersist) {
886
+ let persistPromise = this.persistAndUpdate(instance, {
887
+ clientRequestId: opts?.clientRequestId,
888
+ });
889
+ if (shouldAwaitPersist) {
890
+ persistedResult = await persistPromise;
891
+ }
892
+ }
893
+
894
+ return persistedResult as T | CardErrorJSONAPI;
895
+ }
896
+
897
+ async search(
898
+ query: DataQuery,
899
+ realms?: string[],
900
+ ): Promise<(CardResource<Saved> | FileMetaResource)[]>;
901
+ async search(
902
+ query: DataQuery,
903
+ realms: string[] | undefined,
904
+ opts: {
905
+ includeMeta: true;
906
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
907
+ },
908
+ ): Promise<{
909
+ resources: (CardResource<Saved> | FileMetaResource)[];
910
+ meta: QueryResultsMeta;
911
+ }>;
912
+ async search<T extends CardDef | FileDef = CardDef>(
913
+ query: Query,
914
+ realms?: string[],
915
+ ): Promise<T[]>;
916
+ async search<T extends CardDef | FileDef = CardDef>(
917
+ query: Query,
918
+ realms: string[] | undefined,
919
+ opts: {
920
+ includeMeta: true;
921
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
922
+ },
923
+ ): Promise<{ instances: T[]; meta: QueryResultsMeta }>;
924
+ async search<T extends CardDef | FileDef = CardDef>(
925
+ query: Query,
926
+ realms?: string[],
927
+ opts?: {
928
+ includeMeta?: boolean;
929
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
930
+ },
931
+ ): Promise<
932
+ | T[]
933
+ | (CardResource<Saved> | FileMetaResource)[]
934
+ | { instances: T[]; meta: QueryResultsMeta }
935
+ | {
936
+ resources: (CardResource<Saved> | FileMetaResource)[];
937
+ meta: QueryResultsMeta;
938
+ }
939
+ > {
940
+ let normalizedRealms = (realms ?? [])
941
+ .map((realm) => new RealmPaths(new URL(realm)).url)
942
+ .filter(Boolean);
943
+ let searchRealms =
944
+ normalizedRealms.length > 0
945
+ ? normalizedRealms
946
+ : this.realmServer.availableRealmIdentifiers;
947
+ if (searchRealms.length === 0) {
948
+ if (query.asData) {
949
+ return opts?.includeMeta
950
+ ? { resources: [], meta: { page: { total: 0 } } }
951
+ : [];
952
+ }
953
+ return opts?.includeMeta
954
+ ? { instances: [], meta: { page: { total: 0 } } }
955
+ : [];
956
+ }
957
+ if (query.asData) {
958
+ let result = await this.fetchSearchData(query, searchRealms);
959
+ return opts?.includeMeta ? result : result.resources;
960
+ }
961
+ let result = await this.fetchAndHydrateSearchResults<T>(
962
+ query,
963
+ searchRealms,
964
+ opts?.dependencyTrackingContext,
965
+ );
966
+ return opts?.includeMeta ? result : result.instances;
967
+ }
968
+
969
+ private async fetchAndHydrateSearchResults<
970
+ T extends CardDef | FileDef = CardDef,
971
+ >(
972
+ query: Query,
973
+ realms: string[],
974
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext,
975
+ ): Promise<{ instances: T[]; meta: QueryResultsMeta }> {
976
+ let collectionDoc = await this.fetchSearchDoc(query, realms);
977
+
978
+ // Hydrate each result into the store
979
+ let instances = (
980
+ await Promise.all(
981
+ collectionDoc.data.map(async (resource) => {
982
+ try {
983
+ return await this.addResourceFromSearchData<T>(
984
+ resource,
985
+ dependencyTrackingContext,
986
+ );
987
+ } catch (error) {
988
+ storeLogger.warn(
989
+ `Failed to hydrate resource from search results (id: ${'id' in resource ? resource.id : 'unknown'})`,
990
+ error,
991
+ );
992
+ return undefined;
993
+ }
994
+ }),
995
+ )
996
+ ).filter(Boolean) as T[];
997
+
998
+ return { instances, meta: collectionDoc.meta };
999
+ }
1000
+
1001
+ private async fetchSearchData(
1002
+ query: Query,
1003
+ realms: string[],
1004
+ ): Promise<{
1005
+ resources: (CardResource<Saved> | FileMetaResource)[];
1006
+ meta: QueryResultsMeta;
1007
+ }> {
1008
+ let doc = await this.fetchSearchDoc(query, realms);
1009
+ return { resources: doc.data, meta: doc.meta };
1010
+ }
1011
+
1012
+ // Shared HTTP+JSON path for both `fetchSearchData` (raw resources for
1013
+ // data-only callers) and `fetchAndHydrateSearchResults` (instances
1014
+ // hydrated into the store). Sits between `store.search` and
1015
+ // `_federated-search`.
1016
+ //
1017
+ // Two layers of dedup, both prerender-gated:
1018
+ //
1019
+ // 1. Resolved-doc cache (`searchCache`). Keyed by
1020
+ // (jobId, consumingRealm, query). Same-realm-only so a
1021
+ // cross-realm read can't freeze a value while a peer
1022
+ // realm-server replica swaps mid-job. Hit → return cached doc
1023
+ // synchronously, no network. Miss → fall through.
1024
+ // 2. In-flight Map (`inflightSearch`). Concurrent same-(realms,
1025
+ // query) callers share one pending fetch. Sequential repeats
1026
+ // that don't hit layer 1 still pay the round-trip; layer 1 is
1027
+ // what closes the sequential-repeat window.
1028
+ //
1029
+ // Outside a prerender both layers are bypassed so live-SPA
1030
+ // write-then-read flows keep their current freshness semantics.
1031
+ private async fetchSearchDoc(
1032
+ query: Query,
1033
+ realms: string[],
1034
+ ): Promise<LinkableCollectionDocument> {
1035
+ let inPrerender = Boolean((globalThis as any).__boxelRenderContext);
1036
+ let jobId = inPrerender
1037
+ ? ((globalThis as any).__boxelJobId as string | undefined)
1038
+ : undefined;
1039
+ let consumingRealm = inPrerender
1040
+ ? ((globalThis as any).__boxelConsumingRealm as string | undefined)
1041
+ : undefined;
1042
+
1043
+ // Belt-and-braces jobId-change clear at fetch-entry. `resetState`
1044
+ // and the render-route deactivate hook are the primary paths; this
1045
+ // catches a prerender tab reused across jobs without either firing.
1046
+ if (typeof jobId === 'string' && jobId !== this.searchCacheJobId) {
1047
+ this.searchCache.clear();
1048
+ this.searchCacheJobId = jobId;
1049
+ this.searchCacheGeneration++;
1050
+ }
1051
+
1052
+ // Resolved-doc cache eligibility: prerender + jobId + same-realm.
1053
+ // Cross-realm reads bypass — see field comment.
1054
+ let cacheKey: string | undefined;
1055
+ if (
1056
+ inPrerender &&
1057
+ typeof jobId === 'string' &&
1058
+ typeof consumingRealm === 'string' &&
1059
+ realms.length === 1 &&
1060
+ realms[0] === consumingRealm
1061
+ ) {
1062
+ cacheKey = searchCacheKey(jobId, consumingRealm, query);
1063
+ if (cacheKey !== undefined) {
1064
+ let cached = this.searchCache.get(cacheKey);
1065
+ if (cached !== undefined) {
1066
+ return cached;
1067
+ }
1068
+ }
1069
+ }
1070
+ // Snapshot the generation *after* the entry-time clear so a
1071
+ // concurrent clear arriving during the await below is observable
1072
+ // as a generation drift and we skip the populate. Mirrors the
1073
+ // identity check used by the in-flight Map below.
1074
+ let captureGeneration = this.searchCacheGeneration;
1075
+
1076
+ let inflightKey = inPrerender
1077
+ ? searchInFlightKey(realms, query)
1078
+ : undefined;
1079
+ let doc: LinkableCollectionDocument;
1080
+ if (inflightKey !== undefined) {
1081
+ let existing = this.inflightSearch.get(inflightKey);
1082
+ if (existing) {
1083
+ doc = await existing;
1084
+ } else {
1085
+ let pending = this.fetchSearchDocUncoalesced(query, realms).finally(
1086
+ () => {
1087
+ // Identity-check before deletion: a concurrent
1088
+ // `clearInFlightSearch()` could in principle have removed
1089
+ // (and a later caller re-set) this slot while we were
1090
+ // in-flight. Only clean up if the map still points at *this*
1091
+ // pending promise. Mirrors
1092
+ // `RealmIndexQueryEngine.searchCards` server-side.
1093
+ if (this.inflightSearch.get(inflightKey) === pending) {
1094
+ this.inflightSearch.delete(inflightKey);
1095
+ }
1096
+ },
1097
+ );
1098
+ this.inflightSearch.set(inflightKey, pending);
1099
+ doc = await pending;
1100
+ }
1101
+ } else {
1102
+ doc = await this.fetchSearchDocUncoalesced(query, realms);
1103
+ }
1104
+
1105
+ // Populate only if the cache generation hasn't moved under us. A
1106
+ // route deactivate (clearSearchCache) or `resetState` between
1107
+ // fetch-entry and resolve would bump the generation; in that case
1108
+ // the resolved doc belongs to a now-stale window and must not
1109
+ // repopulate the cleared cache. The caller still receives `doc`
1110
+ // — only the *cache write* is suppressed.
1111
+ if (
1112
+ cacheKey !== undefined &&
1113
+ this.searchCacheGeneration === captureGeneration
1114
+ ) {
1115
+ this.searchCache.set(cacheKey, doc);
1116
+ }
1117
+ return doc;
1118
+ }
1119
+
1120
+ private async fetchSearchDocUncoalesced(
1121
+ query: Query,
1122
+ realms: string[],
1123
+ ): Promise<LinkableCollectionDocument> {
1124
+ let realmServerURLs = this.realmServer.getRealmServersForRealms(realms);
1125
+ // TODO remove this assertion after multi-realm server/federated identity is supported
1126
+ this.realmServer.assertOwnRealmServer(realmServerURLs);
1127
+ let [realmServerURL] = realmServerURLs;
1128
+ let searchURL = new URL('_federated-search', realmServerURL);
1129
+ let response = await this.realmServer.maybeAuthedFetchForRealms(
1130
+ searchURL.href,
1131
+ realms,
1132
+ {
1133
+ method: 'QUERY',
1134
+ headers: {
1135
+ Accept: SupportedMimeType.CardJson,
1136
+ 'Content-Type': 'application/json',
1137
+ ...duringPrerenderHeaders(),
1138
+ ...consumingRealmHeader(),
1139
+ ...jobIdHeader(),
1140
+ ...jobPriorityHeader(),
1141
+ },
1142
+ body: JSON.stringify({ ...query, realms }),
1143
+ },
1144
+ );
1145
+ if (!response.ok) {
1146
+ let responseText = await response.text();
1147
+ let err = new Error(
1148
+ `status: ${response.status} - ${response.statusText}. ${responseText}`,
1149
+ ) as any;
1150
+ err.status = response.status;
1151
+ err.responseText = responseText;
1152
+ err.responseHeaders = response.headers;
1153
+ throw err;
1154
+ }
1155
+ let json = await response.json();
1156
+ if (!isLinkableCollectionDocument(json)) {
1157
+ throw new Error(
1158
+ `The realm search response was not a valid collection document:
1159
+ ${JSON.stringify(json, null, 2)}`,
1160
+ );
1161
+ }
1162
+ return json;
1163
+ }
1164
+
1165
+ getSearchResource<T extends CardDef | FileDef = CardDef>(
1166
+ parent: object,
1167
+ getQuery: () => Query | undefined,
1168
+ getRealms?: () => string[] | undefined,
1169
+ opts?: {
1170
+ isLive?: boolean;
1171
+ doWhileRefreshing?: (() => void) | undefined;
1172
+ dependencyTracking?: RuntimeDependencyTrackingContext;
1173
+ seed?: {
1174
+ cards: T[];
1175
+ searchURL?: string;
1176
+ meta?: QueryResultsMeta;
1177
+ errors?: ErrorEntry[];
1178
+ queryErrors?: Array<{
1179
+ realm: string;
1180
+ type: string;
1181
+ message: string;
1182
+ status?: number;
1183
+ }>;
1184
+ };
1185
+ },
1186
+ ): SearchResource<T> {
1187
+ if (this.isRenderStore && opts) {
1188
+ opts.isLive = false;
1189
+ }
1190
+ return getSearch<T>(parent, getOwner(this)!, getQuery, getRealms, {
1191
+ ...opts,
1192
+ storeService: this,
1193
+ }) as unknown as SearchResource<T>;
1194
+ }
1195
+
1196
+ getSearchDataResource(
1197
+ parent: object,
1198
+ getQuery: () => DataQuery | undefined,
1199
+ getRealms?: () => string[] | undefined,
1200
+ opts?: { isLive?: boolean },
1201
+ ): SearchDataResource {
1202
+ if (this.isRenderStore && opts) {
1203
+ opts.isLive = false;
1204
+ }
1205
+ return getSearchData(
1206
+ parent,
1207
+ getOwner(this)!,
1208
+ getQuery,
1209
+ getRealms,
1210
+ opts,
1211
+ ) as unknown as SearchDataResource;
1212
+ }
1213
+
1214
+ getSaveState(id: string): AutoSaveState | undefined {
1215
+ id = asURL(id);
1216
+ return this.autoSaveStates.get(id);
1217
+ }
1218
+
1219
+ async flush() {
1220
+ await this.ready;
1221
+ await Promise.allSettled(this.newReferencePromises);
1222
+ }
1223
+
1224
+ async flushSaves() {
1225
+ await Promise.allSettled(this.autoSavePromises.values());
1226
+ }
1227
+
1228
+ getReferenceCount(id: string) {
1229
+ id = asURL(id);
1230
+ return this.referenceCount.get(id) ?? 0;
1231
+ }
1232
+
1233
+ isSameId(a: string, b: string): boolean {
1234
+ return a === b || this.peek(a) === this.peek(b);
1235
+ }
1236
+
1237
+ async waitForCardLoad(cardId: string): Promise<void> {
1238
+ let normalizedId = asURL(cardId);
1239
+ if (!normalizedId) {
1240
+ return;
1241
+ }
1242
+ let inflightLoad = this.inflightCardLoads.get(normalizedId);
1243
+ if (inflightLoad) {
1244
+ await inflightLoad.promise;
1245
+ }
1246
+ }
1247
+
1248
+ private startTrackingCardLoad(
1249
+ cardId: string | undefined,
1250
+ ): Deferred<void> | undefined {
1251
+ if (!cardId) {
1252
+ return;
1253
+ }
1254
+ let normalizedId = asURL(cardId);
1255
+ if (!normalizedId) {
1256
+ return;
1257
+ }
1258
+ let deferred = new Deferred<void>();
1259
+ this.inflightCardLoads.set(normalizedId, deferred);
1260
+ return deferred;
1261
+ }
1262
+
1263
+ private finishTrackingCardLoad(
1264
+ cardId: string | undefined,
1265
+ deferred?: Deferred<void>,
1266
+ ) {
1267
+ if (!cardId || !deferred) {
1268
+ return;
1269
+ }
1270
+ let normalizedId = asURL(cardId);
1271
+ if (!normalizedId) {
1272
+ return;
1273
+ }
1274
+ let current = this.inflightCardLoads.get(normalizedId);
1275
+ if (current === deferred) {
1276
+ this.inflightCardLoads.delete(normalizedId);
1277
+ }
1278
+ deferred.fulfill();
1279
+ }
1280
+
1281
+ private async wireUpNewReference(
1282
+ url: string,
1283
+ readType: StoreReadType = 'card',
1284
+ ) {
1285
+ let deferred = new Deferred<void>();
1286
+ await this.withTestWaiters(async () => {
1287
+ this.newReferencePromises.push(deferred.promise);
1288
+ try {
1289
+ await this.ready;
1290
+ if (readType === 'file-meta') {
1291
+ let instanceOrError = await this.getFileMetaInstance<FileDef>({
1292
+ idOrDoc: url,
1293
+ });
1294
+ this.setIdentityContext(
1295
+ instanceOrError as FileDef | CardErrorJSONAPI,
1296
+ 'file-meta',
1297
+ );
1298
+ deferred.fulfill();
1299
+ return;
1300
+ }
1301
+ // Check file-meta map as well as card map — file-meta instances
1302
+ // are loaded into their own map by store.get(id, { type: 'file-meta' })
1303
+ let fileMetaInstance =
1304
+ this.peekError(url, { type: 'file-meta' }) ??
1305
+ this.peek(url, { type: 'file-meta' });
1306
+ if (fileMetaInstance) {
1307
+ // File-meta instances don't need auto-saving or card wiring
1308
+ deferred.fulfill();
1309
+ return;
1310
+ }
1311
+ let instanceOrError = this.peekError(url) ?? this.peek(url);
1312
+ if (!instanceOrError) {
1313
+ instanceOrError = await this.getCardInstance({
1314
+ idOrDoc: url,
1315
+ });
1316
+ this.setIdentityContext(instanceOrError);
1317
+ }
1318
+ await this.startAutoSaving(instanceOrError);
1319
+ if (!instanceOrError.id) {
1320
+ // keep track of urls for cards that are missing
1321
+ this.store.addCardInstanceOrError(url, instanceOrError);
1322
+ }
1323
+ deferred.fulfill();
1324
+ } catch (e) {
1325
+ console.error(
1326
+ `error encountered wiring up new reference for ${JSON.stringify(url)}`,
1327
+ e,
1328
+ );
1329
+ deferred.reject(e);
1330
+ }
1331
+ });
1332
+ }
1333
+
1334
+ /**
1335
+ * Low-level deserialization that throws on validation errors.
1336
+ *
1337
+ * Most callers should use `add()` or `create()` instead — those methods
1338
+ * handle persistence, identity mapping, and auto-saving. This method
1339
+ * bypasses all of that and calls `card-api.createFromSerialized` directly.
1340
+ *
1341
+ * `store.add()` relaxes serialization errors: `Field.validate()` failures
1342
+ * during deserialization are caught internally and logged as console warnings
1343
+ * rather than thrown. This is correct for the UI but not for validation use
1344
+ * cases where errors must propagate. Use this method only when you need
1345
+ * validation errors to throw (e.g., the software-factory's instantiate-card
1346
+ * command which validates that a card instance can be deserialized).
1347
+ */
1348
+ async __dangerousCreateFromSerialized<T extends CardDef>(
1349
+ resource: LooseCardResource,
1350
+ doc: LooseSingleCardDocument | CardDocument,
1351
+ relativeTo?: URL | undefined,
1352
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext,
1353
+ ): Promise<T> {
1354
+ return this.createFromSerialized(
1355
+ resource,
1356
+ doc,
1357
+ relativeTo,
1358
+ dependencyTrackingContext,
1359
+ );
1360
+ }
1361
+
1362
+ private async createFromSerialized<T extends CardDef>(
1363
+ resource: LooseCardResource,
1364
+ doc: LooseSingleCardDocument | CardDocument,
1365
+ relativeTo?: URL | undefined,
1366
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext,
1367
+ ): Promise<T> {
1368
+ let api = await this.cardService.getAPI();
1369
+ let shouldStubTimers =
1370
+ this.renderContextBlocksPersistence() && !isTesting();
1371
+ let performCreate = async () =>
1372
+ (await api.createFromSerialized(resource, doc, relativeTo, {
1373
+ store: this.store,
1374
+ dependencyTrackingContext,
1375
+ })) as T;
1376
+ let card = shouldStubTimers
1377
+ ? await withStubbedRenderTimers(performCreate)
1378
+ : await performCreate();
1379
+ return card;
1380
+ }
1381
+
1382
+ private async setup() {
1383
+ let api = await this.cardService.getAPI();
1384
+ if (isDestroyed(this) || isDestroying(this)) {
1385
+ return;
1386
+ }
1387
+ this.gcInterval = setInterval(
1388
+ () => this.store.sweep(api),
1389
+ 2 * 60_000,
1390
+ ) as unknown as number;
1391
+ }
1392
+
1393
+ private unsubscribeFromInstance(id: string) {
1394
+ let instance = this.store.getCard(id);
1395
+ if (instance && this.cardApiCache) {
1396
+ this.cardApiCache.unsubscribeFromChanges(
1397
+ instance,
1398
+ this.onInstanceUpdated,
1399
+ );
1400
+ }
1401
+
1402
+ // if there are no more subscribers to this realm then unsubscribe from realm
1403
+ let realmHref = !isLocalId(id)
1404
+ ? [...this.subscriptions.keys()].find((realmURL) =>
1405
+ id.startsWith(realmURL),
1406
+ )
1407
+ : undefined;
1408
+ if (!realmHref) {
1409
+ return;
1410
+ }
1411
+
1412
+ let subscription = this.subscriptions.get(realmHref);
1413
+ if (
1414
+ subscription &&
1415
+ ![...this.referenceCount.entries()].find(
1416
+ ([referenceId, count]) =>
1417
+ !isLocalId(referenceId) &&
1418
+ count > 0 &&
1419
+ referenceId.startsWith(realmHref),
1420
+ )
1421
+ ) {
1422
+ subscription.unsubscribe();
1423
+ this.subscriptions.delete(realmHref);
1424
+ }
1425
+ }
1426
+
1427
+ private createCardStore(): CardStore {
1428
+ return new CardStore(this.referenceCount, this.network.authedFetch, {
1429
+ getSearchResource: (parent, getQuery, getRealms, opts) =>
1430
+ this.getSearchResource(parent, getQuery, getRealms, opts),
1431
+ });
1432
+ }
1433
+
1434
+ private handleInvalidations = (event: RealmEventContent) => {
1435
+ if (event.eventName !== 'index') {
1436
+ return;
1437
+ }
1438
+
1439
+ if (event.indexType !== 'incremental') {
1440
+ return;
1441
+ }
1442
+ let invalidations = event.invalidations as string[];
1443
+
1444
+ if (
1445
+ invalidations.find(
1446
+ (i) =>
1447
+ hasExecutableExtension(i) &&
1448
+ this.loaderService.loader.isModuleLoaded(i),
1449
+ )
1450
+ ) {
1451
+ // the invalidation included code changes to modules that are already
1452
+ // loaded. in this case we need to flush the loader so that we can pick
1453
+ // up the updated code before re-running the card. net-new modules that
1454
+ // have never been loaded don't require a loader reset.
1455
+ this.loaderService.resetLoader();
1456
+ this.store.reset();
1457
+ this.reestablishReferences.perform();
1458
+ }
1459
+
1460
+ for (let invalidation of invalidations) {
1461
+ if (hasExecutableExtension(invalidation)) {
1462
+ // we already dealt with this
1463
+ continue;
1464
+ }
1465
+ let fileMetaInstance =
1466
+ this.peekError(invalidation, { type: 'file-meta' }) ??
1467
+ this.peek(invalidation, { type: 'file-meta' });
1468
+ if (fileMetaInstance) {
1469
+ realmEventsLogger.debug(
1470
+ `reloading file-meta resource ${invalidation} because it was previously loaded`,
1471
+ );
1472
+ this.reloadFileMetaTask.perform(invalidation);
1473
+ }
1474
+ let clientRequestId = event.clientRequestId ?? undefined;
1475
+
1476
+ let instance = this.peekError(invalidation) ?? this.peek(invalidation);
1477
+ if (instance) {
1478
+ if (isCardInstance(instance)) {
1479
+ // Do not reload if the event is a result of an instance-editing request that we made. Otherwise we risk
1480
+ // overwriting the inputs with past values. This can happen if the user makes edits in the time between
1481
+ // the auto save request and the arrival realm event.
1482
+ let reloadFile = false;
1483
+
1484
+ if (!clientRequestId) {
1485
+ reloadFile = true;
1486
+ realmEventsLogger.debug(
1487
+ `reloading file resource ${invalidation} because event has no clientRequestId`,
1488
+ );
1489
+ } else if (this.cardService.clientRequestIds.has(clientRequestId)) {
1490
+ if (
1491
+ clientRequestId.startsWith('instance:') ||
1492
+ clientRequestId.startsWith('editor-with-instance')
1493
+ ) {
1494
+ realmEventsLogger.debug(
1495
+ `ignoring invalidation for card ${invalidation} because request id ${clientRequestId} is ours and an instance type`,
1496
+ );
1497
+ } else {
1498
+ reloadFile = true;
1499
+ realmEventsLogger.debug(
1500
+ `reloading file resource ${invalidation} because request id ${clientRequestId} is not instance type`,
1501
+ );
1502
+ }
1503
+ } else {
1504
+ reloadFile = true;
1505
+ realmEventsLogger.debug(
1506
+ `reloading file resource ${invalidation} because request id ${clientRequestId} is not contained within known clientRequestIds`,
1507
+ Array.from(this.cardService.clientRequestIds.values()),
1508
+ );
1509
+ }
1510
+
1511
+ if (reloadFile) {
1512
+ this.reloadTask.perform(instance);
1513
+ } else {
1514
+ realmEventsLogger.debug(
1515
+ `ignoring invalidation ${invalidation} for request id ${clientRequestId}`,
1516
+ );
1517
+ }
1518
+ } else {
1519
+ realmEventsLogger.debug(
1520
+ `reloading file resource ${invalidation} because it is in an error state`,
1521
+ );
1522
+ this.loadInstanceTask.perform(invalidation);
1523
+ }
1524
+ } else {
1525
+ realmEventsLogger.debug(
1526
+ `ignoring invalidation ${invalidation} because we did not previously try to load it`,
1527
+ );
1528
+ }
1529
+ }
1530
+ };
1531
+
1532
+ private loadInstanceTask = task(
1533
+ async (idOrDoc: string | LooseSingleCardDocument) => {
1534
+ let url = asURL(idOrDoc);
1535
+ let reloadTracker = this.startTrackingCardLoad(url);
1536
+ try {
1537
+ let oldInstance = url ? this.store.getCard(url) : undefined;
1538
+ let instanceOrError = await this.getCardInstance({
1539
+ idOrDoc,
1540
+ opts: { noCache: true },
1541
+ });
1542
+ if (oldInstance) {
1543
+ await this.stopAutoSaving(oldInstance);
1544
+ }
1545
+ this.setIdentityContext(instanceOrError);
1546
+ await this.startAutoSaving(instanceOrError);
1547
+ } finally {
1548
+ this.finishTrackingCardLoad(url, reloadTracker);
1549
+ }
1550
+ },
1551
+ );
1552
+
1553
+ private reestablishReferences = task(async () => {
1554
+ let remoteIds = new Set<string>();
1555
+ for (let [id, referenceCount] of this.referenceCount) {
1556
+ if (referenceCount === 0) {
1557
+ continue;
1558
+ }
1559
+ if (isLocalId(id)) {
1560
+ let remoteIdsForLocal = this.store.getRemoteIds(id);
1561
+ if (remoteIdsForLocal.length === 0) {
1562
+ let error = this.store.getCardError(id);
1563
+ if (error?.meta?.remoteId) {
1564
+ remoteIdsForLocal = [error.meta.remoteId];
1565
+ }
1566
+ }
1567
+ for (let remoteId of remoteIdsForLocal) {
1568
+ remoteIds.add(remoteId);
1569
+ }
1570
+ } else {
1571
+ remoteIds.add(id);
1572
+ }
1573
+ }
1574
+ await Promise.all(
1575
+ [...remoteIds].map((id) => this.getCardInstance({ idOrDoc: id })),
1576
+ );
1577
+ });
1578
+
1579
+ private reloadTask = task(async (instance: CardDef) => {
1580
+ let reloadTracker = this.startTrackingCardLoad(instance.id);
1581
+ let maybeReloadedInstance: CardDef | CardErrorJSONAPI | undefined;
1582
+ let isDelete = false;
1583
+
1584
+ try {
1585
+ try {
1586
+ await this.reloadInstance(instance);
1587
+ maybeReloadedInstance = instance;
1588
+ } catch (err: any) {
1589
+ if (err.status === 404) {
1590
+ // in this case the document was invalidated in the index because the
1591
+ // file was deleted
1592
+ isDelete = true;
1593
+ } else {
1594
+ let errorResponse = processCardError(instance.id, err);
1595
+ maybeReloadedInstance = errorResponse.errors[0];
1596
+ }
1597
+ }
1598
+ if (!isCardInstance(maybeReloadedInstance)) {
1599
+ await this.stopAutoSaving(instance);
1600
+ }
1601
+ if (maybeReloadedInstance) {
1602
+ this.setIdentityContext(maybeReloadedInstance);
1603
+ await this.startAutoSaving(maybeReloadedInstance);
1604
+ }
1605
+ if (isDelete) {
1606
+ await this.stopAutoSaving(instance);
1607
+ this.store.delete(instance.id);
1608
+ }
1609
+ } finally {
1610
+ this.finishTrackingCardLoad(instance.id, reloadTracker);
1611
+ }
1612
+ });
1613
+
1614
+ private reloadFileMetaTask = task(async (url: string) => {
1615
+ await this.withTestWaiters(async () => {
1616
+ let instanceOrError = await this.getFileMetaInstance<FileDef>({
1617
+ idOrDoc: url,
1618
+ opts: { noCache: true },
1619
+ });
1620
+ this.setIdentityContext(
1621
+ instanceOrError as FileDef | CardErrorJSONAPI,
1622
+ 'file-meta',
1623
+ );
1624
+ });
1625
+ });
1626
+
1627
+ private onInstanceUpdated = (instance: BaseDef, fieldName: string) => {
1628
+ if (fieldName === 'id') {
1629
+ // id updates are internal and do not trigger autosaves
1630
+ return;
1631
+ }
1632
+ if (isCardInstance(instance)) {
1633
+ let autoSaveState = this.initOrGetAutoSaveState(instance);
1634
+ autoSaveState.hasUnsavedChanges = true;
1635
+ this.doAutoSave(instance);
1636
+ }
1637
+ };
1638
+
1639
+ private setIdentityContext(
1640
+ instanceOrError: CardDef | FileDef | CardErrorJSONAPI,
1641
+ readType: StoreReadType = 'card',
1642
+ ) {
1643
+ if (readType === 'file-meta') {
1644
+ let id = (instanceOrError as { id?: string }).id;
1645
+ if (!id) {
1646
+ return;
1647
+ }
1648
+ this.store.addFileMetaInstanceOrError(
1649
+ id,
1650
+ instanceOrError as FileDef | CardErrorJSONAPI,
1651
+ );
1652
+ return;
1653
+ }
1654
+
1655
+ let instance = isCardInstance(instanceOrError)
1656
+ ? instanceOrError
1657
+ : undefined;
1658
+ if (!instance && !instanceOrError.id) {
1659
+ return;
1660
+ }
1661
+ this.store.addCardInstanceOrError(
1662
+ instance ? (instance.id ?? instance[localIdSymbol]) : instanceOrError.id!, // we checked above to make sure errors have id's
1663
+ instanceOrError as CardDef | CardErrorJSONAPI,
1664
+ );
1665
+ }
1666
+
1667
+ protected async createFileMetaFromSerialized(
1668
+ resource: LooseLinkableResource<FileMetaResource>,
1669
+ doc: LooseSingleResourceDocument<FileMetaResource>,
1670
+ relativeTo: URL | undefined,
1671
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext,
1672
+ ): Promise<FileDef> {
1673
+ let api = await this.cardService.getAPI();
1674
+ let instance = (await api.createFromSerialized(resource, doc, relativeTo, {
1675
+ store: this.store,
1676
+ dependencyTrackingContext,
1677
+ })) as unknown as FileDef;
1678
+ this.setIdentityContext(instance, 'file-meta');
1679
+ return instance;
1680
+ }
1681
+
1682
+ // Internal method for hydrating a resource from search response data.
1683
+ // This avoids N+1 queries when search results include card or file-meta resources.
1684
+ // Not part of the public API since it's meant for internal search result processing.
1685
+ private async addResourceFromSearchData<T extends CardDef | FileDef>(
1686
+ resource: CardResource<Saved> | FileMetaResource,
1687
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext,
1688
+ ): Promise<T | undefined> {
1689
+ if (!resource.id) {
1690
+ throw new Error('resource must have an id');
1691
+ }
1692
+
1693
+ // Handle file-meta resources
1694
+ if (isFileMetaResource(resource)) {
1695
+ let existingInstance = this.peek(resource.id, { type: 'file-meta' });
1696
+ if (existingInstance && isFileDefInstance(existingInstance)) {
1697
+ return existingInstance as T;
1698
+ }
1699
+ let doc = { data: resource };
1700
+ return this.createFileMetaFromSerialized(
1701
+ resource,
1702
+ doc,
1703
+ cardIdToURL(resource.id),
1704
+ dependencyTrackingContext,
1705
+ ) as Promise<T>;
1706
+ }
1707
+
1708
+ // Handle card resources
1709
+ let existingInstance = this.peek(resource.id);
1710
+ if (existingInstance && isCardInstance(existingInstance)) {
1711
+ return existingInstance as T;
1712
+ }
1713
+ // Mark resources that came from `_search` so query-field seed handling can
1714
+ // distinguish unresolved empty seeds from explicit empty card-GET results.
1715
+ (resource as any)[queryFieldSeedFromSearchSymbol] = true;
1716
+ return this.add({ data: resource } as SingleCardDocument, {
1717
+ doNotPersist: true,
1718
+ relativeTo: cardIdToURL(resource.id),
1719
+ dependencyTrackingContext,
1720
+ }) as Promise<T>;
1721
+ }
1722
+
1723
+ private async startAutoSaving(instanceOrError: CardDef | CardErrorJSONAPI) {
1724
+ if (!isCardInstance(instanceOrError)) {
1725
+ return;
1726
+ }
1727
+ let instance = instanceOrError;
1728
+ // module updates will break the cached api. so don't hang on to this longer
1729
+ // than necessary
1730
+ this.cardApiCache = await this.cardService.getAPI();
1731
+ this.cardApiCache.unsubscribeFromChanges(instance, this.onInstanceUpdated);
1732
+ this.cardApiCache.subscribeToChanges(instance, this.onInstanceUpdated);
1733
+ }
1734
+
1735
+ private async stopAutoSaving(instanceOrError: CardDef | CardErrorJSONAPI) {
1736
+ if (!isCardInstance(instanceOrError)) {
1737
+ return;
1738
+ }
1739
+ let instance = instanceOrError;
1740
+ // module updates will break the cached api. so don't hang on to this longer
1741
+ // than necessary
1742
+ this.cardApiCache = await this.cardService.getAPI();
1743
+ this.cardApiCache.unsubscribeFromChanges(instance, this.onInstanceUpdated);
1744
+ this.autoSaveStates.delete(instance.id);
1745
+ this.autoSaveStates.delete(instance[localIdSymbol]);
1746
+ }
1747
+
1748
+ private async getCardInstance<T extends CardDef>({
1749
+ idOrDoc,
1750
+ relativeTo,
1751
+ realm,
1752
+ opts,
1753
+ }: {
1754
+ idOrDoc: string | LooseSingleCardDocument;
1755
+ relativeTo?: URL;
1756
+ realm?: string; // used for new cards
1757
+ opts?: {
1758
+ noCache?: boolean;
1759
+ localDir?: string;
1760
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
1761
+ };
1762
+ }): Promise<T | CardErrorJSONAPI> {
1763
+ let deferred: Deferred<T | CardErrorJSONAPI> | undefined;
1764
+ let id = asURL(idOrDoc);
1765
+ if (id) {
1766
+ let working = this.inflightGetCards.get(id);
1767
+ if (working) {
1768
+ return working as Promise<T | CardErrorJSONAPI>;
1769
+ }
1770
+ deferred = new Deferred<T | CardErrorJSONAPI>();
1771
+ this.inflightGetCards.set(
1772
+ id,
1773
+ deferred.promise as Promise<CardDef | CardErrorJSONAPI>,
1774
+ );
1775
+ }
1776
+ try {
1777
+ if (!id) {
1778
+ if (!this.renderContextBlocksPersistence()) {
1779
+ // this is a new card so instantiate it and save it
1780
+ let doc = idOrDoc as LooseSingleCardDocument;
1781
+ let newInstance = await this.createFromSerialized(
1782
+ doc.data,
1783
+ doc,
1784
+ relativeTo,
1785
+ opts?.dependencyTrackingContext,
1786
+ );
1787
+ let maybeError = await this.persistAndUpdate(newInstance, {
1788
+ realm,
1789
+ localDir: opts?.localDir,
1790
+ });
1791
+ if (!isCardInstance(maybeError)) {
1792
+ return maybeError;
1793
+ }
1794
+ this.store.setCard(newInstance.id, newInstance);
1795
+ deferred?.fulfill(newInstance as T);
1796
+ return newInstance as T;
1797
+ } else {
1798
+ throw new Error(`cannot save serialized doc in render context`);
1799
+ }
1800
+ }
1801
+
1802
+ let existingInstance = this.peek(id);
1803
+ if (!opts?.noCache && existingInstance) {
1804
+ deferred?.fulfill(existingInstance as T | CardErrorJSONAPI);
1805
+ return existingInstance as T;
1806
+ }
1807
+ if (isLocalId(id) && !isRegisteredPrefix(id)) {
1808
+ // we might have lost the local id via a loader refresh, try loading from remote id instead
1809
+ let remoteId = this.store.getRemoteIds(id)?.[0];
1810
+ if (!remoteId) {
1811
+ throw new Error(
1812
+ `instance with local id ${id} does not exist in the store`,
1813
+ );
1814
+ }
1815
+ id = remoteId;
1816
+ }
1817
+ // Resolve registered prefix IDs (e.g. @cardstack/skills/...) to actual
1818
+ // URLs so they can be used for fetching.
1819
+ let url = isRegisteredPrefix(id) ? cardIdToURL(id).href : id;
1820
+ let doc = (typeof idOrDoc !== 'string' ? idOrDoc : undefined) as
1821
+ | SingleCardDocument
1822
+ | undefined;
1823
+ if (!doc) {
1824
+ let json: CardDocument | undefined;
1825
+ if (this.isRenderStore && (globalThis as any).__boxelRenderContext) {
1826
+ let result = await this.cardService.getSource(
1827
+ cardIdToURL(`${url}.json`),
1828
+ );
1829
+ if (result.status === 200) {
1830
+ json = JSON.parse(result.content);
1831
+ } else {
1832
+ throw new Error(
1833
+ `Received non-200 status fetching instance source ${url}.json: ${result.content}`,
1834
+ );
1835
+ }
1836
+ } else {
1837
+ json = await this.cardService.fetchJSON(url);
1838
+ }
1839
+ if (!isSingleCardDocument(json)) {
1840
+ // The URL turned out to be a binary file (e.g. an uploaded
1841
+ // image). The realm-server returns a file-meta JSON document
1842
+ // in that case; reroute to the file-meta load path so the
1843
+ // caller gets a FileDef instead of a hard failure.
1844
+ if (isSingleFileMetaDocument(json)) {
1845
+ // URL was a binary file; reroute to the file-meta bucket.
1846
+ let fileMeta = await this.getFileMetaInstance<FileDef>({
1847
+ idOrDoc: url,
1848
+ opts: {
1849
+ noCache: opts?.noCache,
1850
+ dependencyTrackingContext: opts?.dependencyTrackingContext,
1851
+ },
1852
+ });
1853
+ // Resolve inflightGetCards so concurrent callers don't hang.
1854
+ deferred?.fulfill(fileMeta as unknown as T | CardErrorJSONAPI);
1855
+ return fileMeta as unknown as T;
1856
+ }
1857
+ throw new Error(
1858
+ `bug: server returned a non card document for ${url}:
1859
+ ${JSON.stringify(json, null, 2)}`,
1860
+ );
1861
+ }
1862
+ if (!json.data.id) {
1863
+ // card source format is not serialized with the ID, so we add that back in.
1864
+ json.data.id = url as RealmResourceIdentifier;
1865
+ }
1866
+ if (!json.data.meta?.realmURL) {
1867
+ // Source-mode loads in render context don't include realm metadata.
1868
+ // Query-backed relationship fields require realmURL to build their
1869
+ // fallback search query.
1870
+ let realmURL = this.realm.realmOf(rri(url));
1871
+ if (realmURL) {
1872
+ json.data.meta = {
1873
+ ...(json.data.meta ?? {}),
1874
+ realmURL: realmURL as RealmIdentifier,
1875
+ };
1876
+ }
1877
+ }
1878
+ doc = json;
1879
+ }
1880
+ let instance = await this.createFromSerialized(
1881
+ doc.data,
1882
+ doc,
1883
+ cardIdToURL(doc.data.id!), // instances from the server will have id's
1884
+ opts?.dependencyTrackingContext,
1885
+ );
1886
+ // in case the url is an alias for the id (like index card without the
1887
+ // "/index") we also add this
1888
+ this.store.setCard(url, instance);
1889
+ deferred?.fulfill(instance as T);
1890
+ if (!existingInstance || !isCardInstance(existingInstance)) {
1891
+ this.setIdentityContext(instance);
1892
+ await this.startAutoSaving(instance);
1893
+ }
1894
+ return instance as T;
1895
+ } catch (error: any) {
1896
+ let errorResponse = processCardError(id, error);
1897
+ let cardError = errorResponse.errors[0];
1898
+ deferred?.fulfill(cardError);
1899
+ this.setIdentityContext(cardError);
1900
+ let status = cardError?.status ?? error?.status;
1901
+ let isSystemCardDefault = isSystemCardDefaultId(
1902
+ id,
1903
+ idOrDoc,
1904
+ cardError?.id,
1905
+ );
1906
+ // suppress logging of 404s for system card defaults during tests
1907
+ let shouldLogAsError = !(
1908
+ isTesting() &&
1909
+ status === 404 &&
1910
+ isSystemCardDefault
1911
+ );
1912
+ let message = `error getting instance ${JSON.stringify(idOrDoc, null, 2)}: ${JSON.stringify(error, null, 2)}`;
1913
+ if (shouldLogAsError) {
1914
+ storeLogger.error(message, error);
1915
+ } else {
1916
+ storeLogger.debug(message, error);
1917
+ }
1918
+ return cardError;
1919
+ } finally {
1920
+ if (id) {
1921
+ this.inflightGetCards.delete(id);
1922
+ }
1923
+ }
1924
+ }
1925
+
1926
+ private async getFileMetaInstance<T extends FileDef>({
1927
+ idOrDoc,
1928
+ opts,
1929
+ }: {
1930
+ idOrDoc: string | LooseSingleCardDocument;
1931
+ opts?: {
1932
+ noCache?: boolean;
1933
+ dependencyTrackingContext?: RuntimeDependencyTrackingContext;
1934
+ };
1935
+ }): Promise<T | CardErrorJSONAPI> {
1936
+ let deferred: Deferred<T | CardErrorJSONAPI> | undefined;
1937
+ let id = asURL(idOrDoc);
1938
+ if (!id) {
1939
+ throw new Error('file-meta reads require a URL id');
1940
+ }
1941
+ let working = this.inflightGetFileMeta.get(id);
1942
+ if (working) {
1943
+ return working as Promise<T | CardErrorJSONAPI>;
1944
+ }
1945
+ deferred = new Deferred<T | CardErrorJSONAPI>();
1946
+ this.inflightGetFileMeta.set(
1947
+ id,
1948
+ deferred.promise as Promise<FileDef | CardErrorJSONAPI>,
1949
+ );
1950
+ try {
1951
+ let existingInstance = this.peek(id, { type: 'file-meta' });
1952
+ if (!opts?.noCache && existingInstance) {
1953
+ deferred.fulfill(existingInstance as T | CardErrorJSONAPI);
1954
+ return existingInstance as T | CardErrorJSONAPI;
1955
+ }
1956
+ if (isLocalId(id) && !isRegisteredPrefix(id)) {
1957
+ throw new Error(`file-meta reads do not support local ids (${id})`);
1958
+ }
1959
+ let url = isRegisteredPrefix(id) ? cardIdToURL(id).href : id;
1960
+ let fileMetaDoc: SingleFileMetaDocument | CardError;
1961
+ if (this.isRenderStore && (globalThis as any).__boxelRenderContext) {
1962
+ fileMetaDoc = await this.extractFileMetaDirectly(url);
1963
+ } else {
1964
+ fileMetaDoc = await this.store.loadFileMetaDocument(url, {
1965
+ dependencyTrackingContext: opts?.dependencyTrackingContext,
1966
+ });
1967
+ }
1968
+ if (isCardError(fileMetaDoc)) {
1969
+ throw fileMetaDoc;
1970
+ }
1971
+ let api = await this.cardService.getAPI();
1972
+ let fileInstance = await api.createFromSerialized(
1973
+ fileMetaDoc.data,
1974
+ fileMetaDoc,
1975
+ fileMetaDoc.data.id ? cardIdToURL(fileMetaDoc.data.id) : new URL(url),
1976
+ {
1977
+ store: this.store,
1978
+ dependencyTrackingContext: opts?.dependencyTrackingContext,
1979
+ },
1980
+ );
1981
+ this.setIdentityContext(fileInstance as unknown as FileDef, 'file-meta');
1982
+ deferred.fulfill(fileInstance as T);
1983
+ return fileInstance as T;
1984
+ } catch (error: any) {
1985
+ let errorResponse = processCardError(id, error);
1986
+ let cardError = errorResponse.errors[0];
1987
+ deferred.fulfill(cardError);
1988
+ console.error(
1989
+ `error getting file-meta instance ${JSON.stringify(idOrDoc, null, 2)}: ${JSON.stringify(error, null, 2)}`,
1990
+ error,
1991
+ );
1992
+ return cardError;
1993
+ } finally {
1994
+ this.inflightGetFileMeta.delete(id);
1995
+ }
1996
+ }
1997
+
1998
+ private async extractFileMetaDirectly(
1999
+ url: string,
2000
+ ): Promise<SingleFileMetaDocument | CardError> {
2001
+ let fileDefCodeRef = resolveFileDefCodeRef(new URL(url));
2002
+ let extractor = new FileDefAttributesExtractor({
2003
+ loaderService: this.loaderService,
2004
+ network: this.network,
2005
+ fileURL: url,
2006
+ fileDefCodeRef,
2007
+ baseFileDefCodeRef: baseFileRef,
2008
+ contentHash: undefined,
2009
+ contentSize: undefined,
2010
+ buildError: (errorUrl, error) => {
2011
+ let errorJSONAPI = formattedError(errorUrl, error).errors[0];
2012
+ return errorJsonApiToErrorEntry(errorJSONAPI) as RenderError;
2013
+ },
2014
+ });
2015
+ let result = await extractor.extract();
2016
+ if (result.status === 'error' || !result.resource) {
2017
+ let msg = result.error?.error?.message ?? 'File extract failed';
2018
+ return new CardError(msg, { status: 500 });
2019
+ }
2020
+ return { data: result.resource };
2021
+ }
2022
+
2023
+ // this function is used to determine if the instance will be auto-saved or
2024
+ // note this is a temporary function that is likely to go away with the
2025
+ // creation of completion ephemeral state solution of the store/realm the
2026
+ // only use-case for this function is determining if a preview instance in
2027
+ // catalog realm (which is a read-only), st a card can be mutable without
2028
+ // persisting to the server
2029
+ private useEphemeralState(instance: CardDef | undefined): boolean {
2030
+ if (!instance) {
2031
+ return false;
2032
+ }
2033
+ let realmURL = instance[realmURLSymbol];
2034
+ if (!realmURL) {
2035
+ // if a proper cannot derived, I just revert to the default behavior of auto-save
2036
+ return false;
2037
+ }
2038
+ let permissionToWrite = this.realm.permissions(realmURL.href).canWrite;
2039
+ return !permissionToWrite;
2040
+ }
2041
+
2042
+ private doAutoSave(
2043
+ idOrInstance: string | CardDef,
2044
+ opts?: { isImmediate?: true },
2045
+ ) {
2046
+ let instance: CardDef | undefined;
2047
+ if (typeof idOrInstance === 'string') {
2048
+ let maybeInstance = this.peek(idOrInstance);
2049
+ if (!isCardInstance(maybeInstance)) {
2050
+ return;
2051
+ }
2052
+ instance = maybeInstance;
2053
+ } else {
2054
+ instance = idOrInstance;
2055
+ }
2056
+ if (this.useEphemeralState(instance)) {
2057
+ return;
2058
+ }
2059
+ let autoSaveState = this.initOrGetAutoSaveState(instance);
2060
+ let queueName = instance.id ?? instance[localIdSymbol];
2061
+ let autoSaveQueue = this.autoSaveQueues.get(queueName);
2062
+ if (!autoSaveQueue) {
2063
+ autoSaveQueue = [];
2064
+ this.autoSaveQueues.set(queueName, autoSaveQueue);
2065
+ }
2066
+ autoSaveQueue.push({ ...opts });
2067
+ autoSaveState.isSaving = true;
2068
+ autoSaveState.lastSaveError = undefined;
2069
+ this.drainAutoSaveQueue(queueName);
2070
+ }
2071
+
2072
+ private async drainAutoSaveQueue(queueName: string) {
2073
+ return await this.withTestWaiters(async () => {
2074
+ await this.autoSavePromises.get(queueName);
2075
+
2076
+ let instance = this.peek(queueName);
2077
+ if (!isCardInstance(instance)) {
2078
+ return;
2079
+ }
2080
+ await this.inflightCardMutations.get(instance[localIdSymbol]);
2081
+
2082
+ let done: () => void;
2083
+ this.autoSavePromises.set(
2084
+ queueName,
2085
+ new Promise<void>((r) => (done = r)),
2086
+ );
2087
+ let autoSaves = [...(this.autoSaveQueues.get(queueName) ?? [])];
2088
+ this.autoSaveQueues.set(queueName, []);
2089
+ if (autoSaves && autoSaves.length > 0) {
2090
+ let autoSaveState = this.initOrGetAutoSaveState(instance);
2091
+ // favor isImmediate saves
2092
+ let isImmediate = Boolean(autoSaves.find((a) => a.isImmediate));
2093
+ try {
2094
+ let maybeError = await this.saveInstance(
2095
+ instance,
2096
+ isImmediate ? { isImmediate } : undefined,
2097
+ );
2098
+ autoSaveState.hasUnsavedChanges = false;
2099
+ autoSaveState.lastSaved = Date.now();
2100
+ autoSaveState.lastSavedErrorMsg = undefined;
2101
+ autoSaveState.lastSaveError =
2102
+ maybeError && !isCardInstance(maybeError) ? maybeError : undefined;
2103
+ } catch (error) {
2104
+ // error will already be logged in CardService
2105
+ if (autoSaveState) {
2106
+ autoSaveState.lastSaveError = error as Error;
2107
+ }
2108
+ } finally {
2109
+ autoSaveState.isSaving = false;
2110
+ this.calculateLastSavedMsg(autoSaveState);
2111
+ if (isLocalId(queueName) && instance.id) {
2112
+ this.autoSaveStates.set(instance.id, autoSaveState);
2113
+ }
2114
+ }
2115
+ }
2116
+ done!();
2117
+ });
2118
+ }
2119
+
2120
+ private initOrGetAutoSaveState(instance: CardDef): AutoSaveState {
2121
+ let autoSaveState = this.autoSaveStates.get(
2122
+ instance.id ?? instance[localIdSymbol],
2123
+ );
2124
+ if (!autoSaveState) {
2125
+ autoSaveState = new TrackedObject({
2126
+ isSaving: false,
2127
+ hasUnsavedChanges: false,
2128
+ lastSaved: undefined,
2129
+ lastSavedErrorMsg: undefined,
2130
+ lastSaveError: undefined,
2131
+ });
2132
+ this.autoSaveStates.set(instance[localIdSymbol], autoSaveState);
2133
+ }
2134
+ if (instance.id && !this.autoSaveStates.get(instance.id)) {
2135
+ this.autoSaveStates.set(instance.id, autoSaveState);
2136
+ }
2137
+ return autoSaveState;
2138
+ }
2139
+
2140
+ private async saveInstance(instance: CardDef, opts?: { isImmediate?: true }) {
2141
+ if (this.renderContextBlocksPersistence()) {
2142
+ // we skip saving when rendering cards in headless chrome
2143
+ return;
2144
+ }
2145
+ if (opts?.isImmediate) {
2146
+ return await this.persistAndUpdate(instance);
2147
+ } else {
2148
+ // these saves can happen so fast that we'll make sure to wait at
2149
+ // least 500ms for human consumption
2150
+ let [result] = await Promise.all([
2151
+ this.persistAndUpdate(instance),
2152
+ delay(500),
2153
+ ]);
2154
+ return result;
2155
+ }
2156
+ }
2157
+
2158
+ private async saveCardDocument(
2159
+ doc: LooseSingleCardDocument,
2160
+ opts?: PersistOptions,
2161
+ ): Promise<SingleCardDocument> {
2162
+ let isSaved = !!doc.data.id;
2163
+ let url = resolveDocUrl(doc.data.id, opts?.realm, opts?.localDir);
2164
+ let json = await this.cardService.fetchJSON(url, {
2165
+ method: isSaved ? 'PATCH' : 'POST',
2166
+ body: JSON.stringify(doc, null, 2),
2167
+ headers: {
2168
+ 'Content-Type': SupportedMimeType.CardJson,
2169
+ },
2170
+ clientRequestId: opts?.clientRequestId,
2171
+ });
2172
+ if (!isSingleCardDocument(json)) {
2173
+ throw new Error(
2174
+ `bug: arg is not a card document:
2175
+ ${JSON.stringify(json, null, 2)}`,
2176
+ );
2177
+ }
2178
+ return json;
2179
+ }
2180
+
2181
+ private calculateLastSavedMsg(autoSaveState: AutoSaveState) {
2182
+ let savedMessage: string | undefined;
2183
+ if (autoSaveState.lastSaveError) {
2184
+ savedMessage = `Failed to save: ${this.getErrorMessage(
2185
+ autoSaveState.lastSaveError,
2186
+ )}`;
2187
+ } else if (autoSaveState.lastSaved) {
2188
+ savedMessage = `Saved ${formatDistanceToNow(autoSaveState.lastSaved, {
2189
+ addSuffix: true,
2190
+ })}`;
2191
+ }
2192
+ if (autoSaveState.lastSavedErrorMsg != savedMessage) {
2193
+ autoSaveState.lastSavedErrorMsg = savedMessage;
2194
+ }
2195
+ }
2196
+
2197
+ private getErrorMessage(error: CardErrorJSONAPI | Error) {
2198
+ if (
2199
+ 'meta' in error &&
2200
+ typeof error.meta === 'object' &&
2201
+ 'responseHeaders' in error.meta &&
2202
+ error.meta.responseHeaders &&
2203
+ typeof error.meta.responseHeaders === 'object'
2204
+ ) {
2205
+ let wafRule = Object.entries(error.meta.responseHeaders).find(
2206
+ ([header]) => header.toLowerCase() === 'x-blocked-by-waf-rule',
2207
+ )?.[1];
2208
+ if (wafRule) {
2209
+ return `Request blocked by Web Application Firewall. X-blocked-by-waf-rule response header specifies rule: ${wafRule}`;
2210
+ }
2211
+ }
2212
+ if (error.message) {
2213
+ return error.message;
2214
+ }
2215
+ return 'Unknown error';
2216
+ }
2217
+
2218
+ private async persistAndUpdate(
2219
+ instance: CardDef,
2220
+ opts?: PersistOptions,
2221
+ ): Promise<CardDef | CardErrorJSONAPI> {
2222
+ return await this.withTestWaiters(async () => {
2223
+ let isNew = !instance.id;
2224
+ let inflightMutation = this.inflightCardMutations.get(
2225
+ instance[localIdSymbol],
2226
+ );
2227
+ if (inflightMutation) {
2228
+ // the local instance is always up-to-date, but things can get messy if
2229
+ // we try to update an instance that is in the process of being created on
2230
+ // the server, because then it still looks like to the client another
2231
+ // POST should be issued when instead we really want to PATCH.
2232
+ await inflightMutation;
2233
+ }
2234
+ let deferred = new Deferred<void>();
2235
+ this.inflightCardMutations.set(instance[localIdSymbol], deferred.promise);
2236
+ try {
2237
+ let doc = await this.cardService.serializeCard(instance, {
2238
+ // for a brand new card that has no id yet, we don't know what we are
2239
+ // relativeTo because its up to the realm server to assign us an ID, so
2240
+ // URL's should be absolute
2241
+ useAbsoluteURL: true,
2242
+ withIncluded: true,
2243
+ omitQueryFields: true,
2244
+ });
2245
+
2246
+ // send doc over the wire with absolute URL's. The realm server will convert
2247
+ // to relative URL's as it serializes the cards
2248
+ let realmURL = instance[realmURLSymbol];
2249
+ // in the case where we get no realm URL from the card, we are dealing with
2250
+ // a new card instance that does not have a realm URL yet.
2251
+ if (!realmURL) {
2252
+ let defaultRealmHref =
2253
+ opts?.realm ?? this.realm.defaultWritableRealm?.path;
2254
+ if (!defaultRealmHref) {
2255
+ throw new Error('Could not find a writable realm');
2256
+ }
2257
+ realmURL = new URL(defaultRealmHref);
2258
+ }
2259
+ let json = await this.saveCardDocument(doc, {
2260
+ realm: realmURL.href,
2261
+ localDir: opts?.localDir,
2262
+ clientRequestId: opts?.clientRequestId,
2263
+ });
2264
+
2265
+ let api = await this.cardService.getAPI();
2266
+ // the store state represents the latest state and the server state is
2267
+ // potentially out-of-date. As such we only merge the server state that
2268
+ // the store does not know about specifically remote ID's and realm
2269
+ // meta. the attributes and relationships state from the server are
2270
+ // thrown away since the store has a more recent version of these.
2271
+ if (needsServerStateMerge(instance, json)) {
2272
+ let serverState = cloneDeep(json);
2273
+ delete serverState.data.attributes;
2274
+ delete serverState.data.relationships;
2275
+ await api.updateFromSerialized(instance, serverState, this.store);
2276
+ }
2277
+ if (isNew) {
2278
+ api.setId(instance, json.data.id!);
2279
+ this.subscribeToRealm(rri(instance.id));
2280
+ this.operatorModeStateService.handleCardIdAssignment(
2281
+ instance[localIdSymbol],
2282
+ );
2283
+ await this.updateForeignConsumersOf(instance);
2284
+ this.setIdentityContext(instance);
2285
+ await this.startAutoSaving(instance);
2286
+ }
2287
+ if (this.onSaveSubscriber) {
2288
+ this.onSaveSubscriber(cardIdToURL(json.data.id!), json);
2289
+ }
2290
+ return instance;
2291
+ } catch (err) {
2292
+ console.error(`Failed to save ${instance.id}: `, err);
2293
+ let errorResponse = processCardError(
2294
+ instance.id ?? instance[localIdSymbol],
2295
+ err,
2296
+ );
2297
+ let cardError = errorResponse.errors[0];
2298
+ this.setIdentityContext(cardError);
2299
+ let remoteId = cardError.meta?.remoteId;
2300
+ if (remoteId && (!cardError.id || isLocalId(cardError.id))) {
2301
+ this.store.addCardInstanceOrError(remoteId, cardError);
2302
+ }
2303
+ return cardError;
2304
+ } finally {
2305
+ deferred.fulfill();
2306
+ }
2307
+ });
2308
+ }
2309
+
2310
+ // in the case we are making a cross realm relationship with a link that
2311
+ // hasn't been saved yet, as soon as the link does actually get saved we need
2312
+ // to inform the consuming instances that live in different realms of the new
2313
+ // link's remote id and have those consumers update in their respective
2314
+ // realms.
2315
+ private async updateForeignConsumersOf(instance: CardDef) {
2316
+ let consumers = this.store.consumersOf(
2317
+ await this.cardService.getAPI(),
2318
+ instance,
2319
+ );
2320
+ let instanceRealm = instance[realmURLSymbol]?.href;
2321
+ if (!instanceRealm) {
2322
+ return;
2323
+ }
2324
+
2325
+ for (let consumer of consumers) {
2326
+ let consumerRealm = consumer[realmURLSymbol]?.href;
2327
+ if (consumerRealm !== instanceRealm && consumer.id) {
2328
+ this.save(consumer.id);
2329
+ }
2330
+ }
2331
+ }
2332
+
2333
+ private async reloadInstance(instance: CardDef): Promise<void> {
2334
+ // we don't await this in the realm subscription callback, so this test
2335
+ // waiter should catch otherwise leaky async in the tests
2336
+ await this.withTestWaiters(async () => {
2337
+ let api = await this.cardService.getAPI();
2338
+ let incomingDoc: SingleCardDocument = (await this.cardService.fetchJSON(
2339
+ instance.id,
2340
+ undefined,
2341
+ )) as SingleCardDocument;
2342
+
2343
+ if (!isSingleCardDocument(incomingDoc)) {
2344
+ throw new Error(
2345
+ `bug: server returned a non card document for ${instance.id}:
2346
+ ${JSON.stringify(incomingDoc, null, 2)}`,
2347
+ );
2348
+ }
2349
+ await api.updateFromSerialized<typeof CardDef>(
2350
+ instance,
2351
+ incomingDoc,
2352
+ this.store,
2353
+ );
2354
+ });
2355
+ }
2356
+
2357
+ private subscribeToRealm(url: RealmResourceIdentifier | URL) {
2358
+ if (this.hostModeService.isActive) {
2359
+ return;
2360
+ }
2361
+
2362
+ let realmURL = this.realm.realmOf(url);
2363
+ if (!realmURL) {
2364
+ console.warn(
2365
+ `could not determine realm for card ${url instanceof URL ? url.href : url} when trying to subscribe to realm`,
2366
+ );
2367
+ return;
2368
+ }
2369
+ let subscription = this.subscriptions.get(realmURL);
2370
+ if (!subscription) {
2371
+ this.subscriptions.set(realmURL, {
2372
+ unsubscribe: this.messageService.subscribe(realmURL, (event) =>
2373
+ this.handleInvalidations(event),
2374
+ ),
2375
+ });
2376
+ }
2377
+ }
2378
+
2379
+ private async loadPatchedInstances(
2380
+ patchData: PatchData,
2381
+ relativeTo: URL | undefined,
2382
+ ): Promise<{
2383
+ [fieldName: string]: CardDef | CardDef[];
2384
+ }> {
2385
+ if (!patchData?.relationships) {
2386
+ return {};
2387
+ }
2388
+ let result: { [fieldName: string]: CardDef | CardDef[] } = {};
2389
+ await Promise.all(
2390
+ Object.entries(patchData.relationships).map(async ([fieldName, rel]) => {
2391
+ if (Array.isArray(rel)) {
2392
+ let instances: CardDef[] = [];
2393
+ await Promise.all(
2394
+ rel.map(async (r) => {
2395
+ let instance = await this.loadRelationshipInstance(r, relativeTo);
2396
+ if (instance) {
2397
+ instances.push(instance);
2398
+ }
2399
+ }),
2400
+ );
2401
+ result[fieldName] = instances;
2402
+ } else {
2403
+ let instance = await this.loadRelationshipInstance(rel, relativeTo);
2404
+ if (instance) {
2405
+ result[fieldName] = instance;
2406
+ }
2407
+ }
2408
+ }),
2409
+ );
2410
+ return result;
2411
+ }
2412
+
2413
+ private async loadRelationshipInstance(
2414
+ rel: Relationship,
2415
+ relativeTo: URL | undefined,
2416
+ ) {
2417
+ if (!rel.links?.self) {
2418
+ return;
2419
+ }
2420
+ let id = rel.links.self;
2421
+ let instance = await this.getCardInstance({
2422
+ idOrDoc: resolveCardReference(id, relativeTo),
2423
+ });
2424
+ return isCardInstance(instance) ? instance : undefined;
2425
+ }
2426
+
2427
+ private async withTestWaiters<T>(cb: () => Promise<T>) {
2428
+ let token = waiter.beginAsync();
2429
+ try {
2430
+ let result = await cb();
2431
+ // only do this in test env--this makes sure that we also wait for any
2432
+ // interior card instance async as part of our ember-test-waiters
2433
+ if (isTesting()) {
2434
+ await this.cardService.cardsSettled();
2435
+ }
2436
+ return result;
2437
+ } finally {
2438
+ waiter.endAsync(token);
2439
+ }
2440
+ }
2441
+ }
2442
+
2443
+ function processCardError(
2444
+ url: string | undefined,
2445
+ error: any,
2446
+ ): CardErrorsJSONAPI {
2447
+ try {
2448
+ let errorResponse = JSON.parse(error.responseText);
2449
+ return formattedError(url, error, errorResponse.errors?.[0]);
2450
+ } catch (parseError) {
2451
+ switch (error.status) {
2452
+ // tailor HTTP responses as necessary for better user feedback
2453
+ case 404:
2454
+ return formattedError(url, error, {
2455
+ status: 404,
2456
+ title: 'Card Not Found',
2457
+ message: `The card ${url} does not exist`,
2458
+ });
2459
+ default:
2460
+ return formattedError(url, error, undefined);
2461
+ }
2462
+ }
2463
+ }
2464
+
2465
+ function needsServerStateMerge(
2466
+ instance: CardDef,
2467
+ serverState: SingleCardDocument,
2468
+ ): boolean {
2469
+ return (
2470
+ instance.id !== serverState.data.id ||
2471
+ !isEqual(instance[meta]?.realmInfo, serverState.data.meta.realmInfo)
2472
+ );
2473
+ }
2474
+
2475
+ export function asURL(urlOrDoc: string): string;
2476
+ export function asURL(urlOrDoc: LooseSingleCardDocument): string | undefined;
2477
+ export function asURL(
2478
+ urlOrDoc: string | LooseSingleCardDocument,
2479
+ ): string | undefined;
2480
+ export function asURL(urlOrDoc: string | LooseSingleCardDocument) {
2481
+ if (typeof urlOrDoc !== 'string') {
2482
+ return urlOrDoc.data.id;
2483
+ }
2484
+ let id = urlOrDoc.replace(/\.json$/, '');
2485
+ return isLocalId(id) ? id : resolveCardReference(id, undefined);
2486
+ }
2487
+
2488
+ function isSystemCardDefaultId(
2489
+ id: string | undefined,
2490
+ idOrDoc: string | LooseSingleCardDocument,
2491
+ errorId: string | undefined,
2492
+ ): boolean {
2493
+ let candidates = [
2494
+ id,
2495
+ typeof idOrDoc === 'string' ? idOrDoc : idOrDoc?.data?.id,
2496
+ errorId,
2497
+ ].filter(Boolean) as string[];
2498
+ return candidates.some((candidate) =>
2499
+ candidate.includes('/SystemCard/default'),
2500
+ );
2501
+ }
2502
+
2503
+ async function withStubbedRenderTimers<T>(cb: () => Promise<T>): Promise<T> {
2504
+ if (typeof window === 'undefined' || isTesting()) {
2505
+ return await cb();
2506
+ }
2507
+ // Prevent cards that use timers (e.g. timers-card.gts) from continuing to
2508
+ // execute after we capture their HTML during prerender. In the browser we
2509
+ // normally let timers run, but in the render route we need deterministic,
2510
+ // single-shot renders so runaway timers don't crash indexing.
2511
+ let restore = enableRenderTimerStub();
2512
+ try {
2513
+ return await withTimersBlocked(cb);
2514
+ } finally {
2515
+ restore();
2516
+ }
2517
+ }
2518
+
2519
+ // Resolves either to
2520
+ // - an instance
2521
+ // - a directory
2522
+ function resolveDocUrl(id?: string, realm?: string, local?: string) {
2523
+ if (id) {
2524
+ return id;
2525
+ }
2526
+ if (!realm) {
2527
+ throw new Error('Cannot resolve target url without a realm');
2528
+ }
2529
+ let path = new RealmPaths(new URL(realm));
2530
+ if (local) {
2531
+ return path.directoryURL(local).href;
2532
+ }
2533
+ return path.url;
2534
+ }
2535
+
2536
+ declare module '@ember/service' {
2537
+ interface Registry {
2538
+ store: StoreService;
2539
+ }
2540
+ }