@yahoo/uds-create-config 2.44.0 → 3.0.0

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 (376) hide show
  1. package/README.md +129 -0
  2. package/dist/ai-lanes.d.ts +23 -0
  3. package/dist/ai-lanes.js +34 -0
  4. package/dist/authoring/define-component.d.ts +85 -0
  5. package/dist/configs/CanvasConfig.d.ts +3237 -0
  6. package/dist/configs/CanvasConfig.js +332 -0
  7. package/dist/configs/build-options.d.ts +10 -0
  8. package/dist/configs/build-options.js +68 -0
  9. package/dist/configs/platform.d.ts +15 -0
  10. package/dist/configs/platform.js +17 -0
  11. package/dist/configs/react-native-system.d.ts +3026 -0
  12. package/dist/configs/react-native-system.js +64 -0
  13. package/dist/configs/system.d.ts +2771 -0
  14. package/dist/configs/system.js +74 -0
  15. package/dist/css/properties.generated.d.ts +14 -0
  16. package/dist/css/properties.generated.js +6260 -0
  17. package/dist/css/value-type.d.ts +48 -0
  18. package/dist/css/value-type.js +131 -0
  19. package/dist/css/values.d.ts +33 -0
  20. package/dist/css/values.js +116 -0
  21. package/dist/entities/native/NativeModifier.d.ts +25 -0
  22. package/dist/entities/native/NativeModifier.js +41 -0
  23. package/dist/entities/native/NativeSettings.d.ts +44 -0
  24. package/dist/entities/native/NativeSettings.js +39 -0
  25. package/dist/entities/native/NativeStyleProperty.d.ts +355 -0
  26. package/dist/entities/native/NativeStyleProperty.js +28 -0
  27. package/dist/entities/native/NativeToken.d.ts +31 -0
  28. package/dist/entities/native/NativeToken.js +49 -0
  29. package/dist/entities/system/CanvasRole.d.ts +34 -0
  30. package/dist/entities/system/CanvasRole.js +45 -0
  31. package/dist/entities/system/Component.d.ts +1287 -0
  32. package/dist/entities/system/Component.js +1840 -0
  33. package/dist/entities/system/Composite.d.ts +12 -0
  34. package/dist/entities/system/Composite.js +26 -0
  35. package/dist/entities/system/Device.d.ts +11 -0
  36. package/dist/entities/system/Device.js +18 -0
  37. package/dist/entities/system/File.d.ts +72 -0
  38. package/dist/entities/system/File.js +136 -0
  39. package/dist/entities/system/Font.d.ts +48 -0
  40. package/dist/entities/system/Font.js +56 -0
  41. package/dist/entities/system/GlobalStyle.d.ts +26 -0
  42. package/dist/entities/system/GlobalStyle.js +54 -0
  43. package/dist/entities/system/Guidance.d.ts +85 -0
  44. package/dist/entities/system/Guidance.js +59 -0
  45. package/dist/entities/system/GuidanceStyle.d.ts +14 -0
  46. package/dist/entities/system/GuidanceStyle.js +8 -0
  47. package/dist/entities/system/Icon.d.ts +46 -0
  48. package/dist/entities/system/Icon.js +46 -0
  49. package/dist/entities/system/LinkedSystem.d.ts +504 -0
  50. package/dist/entities/system/LinkedSystem.js +814 -0
  51. package/dist/entities/system/Modifier.d.ts +92 -0
  52. package/dist/entities/system/Modifier.js +120 -0
  53. package/dist/entities/system/Motion.d.ts +28 -0
  54. package/dist/entities/system/Motion.js +48 -0
  55. package/dist/entities/system/Operation.d.ts +100 -0
  56. package/dist/entities/system/Operation.js +236 -0
  57. package/dist/entities/system/Package.d.ts +42 -0
  58. package/dist/entities/system/Package.js +75 -0
  59. package/dist/entities/system/Settings.d.ts +94 -0
  60. package/dist/entities/system/Settings.js +166 -0
  61. package/dist/entities/system/StyleProperty.d.ts +135 -0
  62. package/dist/entities/system/StyleProperty.js +438 -0
  63. package/dist/entities/system/Token.d.ts +67 -0
  64. package/dist/entities/system/Token.js +129 -0
  65. package/dist/entities/system/Tool.d.ts +134 -0
  66. package/dist/entities/system/Tool.js +124 -0
  67. package/dist/entities/system/color.d.ts +64 -0
  68. package/dist/entities/system/color.js +123 -0
  69. package/dist/entities/system/component-module.d.ts +63 -0
  70. package/dist/entities/system/component-module.js +258 -0
  71. package/dist/entities/system/copy-plan.d.ts +98 -0
  72. package/dist/entities/system/copy-plan.js +451 -0
  73. package/dist/entities/system/defineComponent.d.ts +428 -0
  74. package/dist/entities/system/defineComponent.js +337 -0
  75. package/dist/entities/system/element.d.ts +101 -0
  76. package/dist/entities/system/element.js +171 -0
  77. package/dist/entities/system/icon-library.d.ts +64 -0
  78. package/dist/entities/system/icon-library.js +112 -0
  79. package/dist/entities/system/icon-metadata.d.ts +72 -0
  80. package/dist/entities/system/icon-metadata.js +111 -0
  81. package/dist/entities/system/link-declaration.d.ts +27 -0
  82. package/dist/entities/system/link-declaration.js +64 -0
  83. package/dist/entities/system/link-plan.d.ts +160 -0
  84. package/dist/entities/system/link-plan.js +770 -0
  85. package/dist/entities/system/runtime-modules.d.ts +25 -0
  86. package/dist/entities/system/runtime-modules.js +38 -0
  87. package/dist/entities/system/style-bag.d.ts +20 -0
  88. package/dist/entities/system/style-bag.js +246 -0
  89. package/dist/entities/system/token-index.d.ts +89 -0
  90. package/dist/entities/system/token-index.js +112 -0
  91. package/dist/framework/Config.d.ts +2472 -0
  92. package/dist/framework/Config.js +5023 -0
  93. package/dist/framework/Entity.d.ts +127 -0
  94. package/dist/framework/Entity.js +125 -0
  95. package/dist/framework/authoring.d.ts +24 -0
  96. package/dist/framework/authoring.js +7 -0
  97. package/dist/framework/change-hooks.d.ts +119 -0
  98. package/dist/framework/change-hooks.js +131 -0
  99. package/dist/framework/changes.d.ts +88 -0
  100. package/dist/framework/changes.js +246 -0
  101. package/dist/framework/class-names.d.ts +260 -0
  102. package/dist/framework/class-names.js +630 -0
  103. package/dist/framework/coalesce.d.ts +25 -0
  104. package/dist/framework/coalesce.js +60 -0
  105. package/dist/framework/config-op.d.ts +76 -0
  106. package/dist/framework/config-op.js +43 -0
  107. package/dist/framework/config-operation-kind.d.ts +14 -0
  108. package/dist/framework/config-operation-kind.js +14 -0
  109. package/dist/framework/config-ops.d.ts +34 -0
  110. package/dist/framework/config-ops.js +187 -0
  111. package/dist/framework/defineConfig.d.ts +196 -0
  112. package/dist/framework/defineConfig.js +100 -0
  113. package/dist/framework/defineDerivedEntity.d.ts +74 -0
  114. package/dist/framework/defineDerivedEntity.js +83 -0
  115. package/dist/framework/defineEntity.d.ts +318 -0
  116. package/dist/framework/defineEntity.js +183 -0
  117. package/dist/framework/defineSubEntity.d.ts +148 -0
  118. package/dist/framework/defineSubEntity.js +121 -0
  119. package/dist/framework/derived-mutations.d.ts +20 -0
  120. package/dist/framework/derived-mutations.js +38 -0
  121. package/dist/framework/entity-search.d.ts +29 -0
  122. package/dist/framework/entity-search.js +117 -0
  123. package/dist/framework/inline-sources.d.ts +49 -0
  124. package/dist/framework/inline-sources.js +388 -0
  125. package/dist/framework/layer-styles.d.ts +25 -0
  126. package/dist/framework/layer-styles.js +49 -0
  127. package/dist/framework/memo.d.ts +48 -0
  128. package/dist/framework/memo.js +84 -0
  129. package/dist/framework/overlay.d.ts +49 -0
  130. package/dist/framework/overlay.js +17 -0
  131. package/dist/framework/plan.d.ts +49 -0
  132. package/dist/framework/plan.js +73 -0
  133. package/dist/framework/projections.d.ts +480 -0
  134. package/dist/framework/projections.js +974 -0
  135. package/dist/framework/prop-surface.d.ts +132 -0
  136. package/dist/framework/prop-surface.js +278 -0
  137. package/dist/framework/ref-graph.d.ts +71 -0
  138. package/dist/framework/ref-graph.js +158 -0
  139. package/dist/framework/ref-integrity.d.ts +55 -0
  140. package/dist/framework/ref-integrity.js +171 -0
  141. package/dist/framework/registered.d.ts +95 -0
  142. package/dist/framework/registry-dir.d.ts +42 -0
  143. package/dist/framework/registry-dir.js +48 -0
  144. package/dist/framework/registry.d.ts +300 -0
  145. package/dist/framework/registry.js +104 -0
  146. package/dist/framework/rejection.d.ts +58 -0
  147. package/dist/framework/rejection.js +111 -0
  148. package/dist/framework/rename-inference.d.ts +26 -0
  149. package/dist/framework/rename-inference.js +50 -0
  150. package/dist/framework/render-spec.d.ts +296 -0
  151. package/dist/framework/render-spec.js +809 -0
  152. package/dist/framework/schema-version.d.ts +140 -0
  153. package/dist/framework/schema-version.js +214 -0
  154. package/dist/framework/schemas.d.ts +109 -0
  155. package/dist/framework/schemas.js +615 -0
  156. package/dist/framework/session.d.ts +196 -0
  157. package/dist/framework/session.js +367 -0
  158. package/dist/framework/signature.d.ts +15 -0
  159. package/dist/framework/signature.js +83 -0
  160. package/dist/framework/snapshot.d.ts +115 -0
  161. package/dist/framework/snapshot.js +277 -0
  162. package/dist/framework/source-integrity.d.ts +226 -0
  163. package/dist/framework/source-integrity.js +478 -0
  164. package/dist/framework/sources.d.ts +211 -0
  165. package/dist/framework/sources.js +89 -0
  166. package/dist/framework/utils/compiled-schema.d.ts +23 -0
  167. package/dist/framework/utils/compiled-schema.js +46 -0
  168. package/dist/framework/utils/content-key.d.ts +15 -0
  169. package/dist/framework/utils/content-key.js +33 -0
  170. package/dist/framework/utils/enumerated.d.ts +13 -0
  171. package/dist/framework/utils/enumerated.js +19 -0
  172. package/dist/framework/utils/field-path.d.ts +95 -0
  173. package/dist/framework/utils/field-path.js +136 -0
  174. package/dist/framework/utils/field-schema.d.ts +55 -0
  175. package/dist/framework/utils/field-schema.js +183 -0
  176. package/dist/framework/utils/group.d.ts +37 -0
  177. package/dist/framework/utils/group.js +13 -0
  178. package/dist/framework/utils/package-path.d.ts +37 -0
  179. package/dist/framework/utils/package-path.js +61 -0
  180. package/dist/framework/utils/refs.d.ts +194 -0
  181. package/dist/framework/utils/refs.js +403 -0
  182. package/dist/framework/validate-spec.d.ts +27 -0
  183. package/dist/framework/validate-spec.js +45 -0
  184. package/dist/framework/value-domain.d.ts +26 -0
  185. package/dist/framework/value-domain.js +202 -0
  186. package/dist/framework/views-facade.d.ts +15 -0
  187. package/dist/framework/views-facade.js +19 -0
  188. package/dist/index.d.ts +90 -42
  189. package/dist/index.js +86 -37
  190. package/dist/migrations/2.0.0/raw-css.d.ts +11 -0
  191. package/dist/migrations/2.0.0/raw-css.js +72 -0
  192. package/dist/migrations/2.0.0/v1-artifact.d.ts +1559 -0
  193. package/dist/migrations/2.0.0/v1-artifact.js +1018 -0
  194. package/dist/migrations/20260908171448_settings_as_kind.d.ts +22 -0
  195. package/dist/migrations/20260908171448_settings_as_kind.js +89 -0
  196. package/dist/migrations/20260908194245_visible_state_pointer.d.ts +19 -0
  197. package/dist/migrations/20260908194245_visible_state_pointer.js +115 -0
  198. package/dist/migrations/20260909183001_icon_knockout_variants.d.ts +18 -0
  199. package/dist/migrations/20260909183001_icon_knockout_variants.js +8 -0
  200. package/dist/migrations/20260910212453_linked_system_link_ops.d.ts +13 -0
  201. package/dist/migrations/20260910212453_linked_system_link_ops.js +8 -0
  202. package/dist/migrations/index.d.ts +2 -0
  203. package/dist/migrations/index.js +2 -0
  204. package/dist/react-native/style-keys.generated.d.ts +10 -0
  205. package/dist/react-native/style-keys.generated.js +167 -0
  206. package/dist/renderer/RendererErrorBoundary.d.ts +32 -5
  207. package/dist/renderer/RendererErrorBoundary.js +59 -10
  208. package/dist/renderer/UdsRenderer.d.ts +11 -10
  209. package/dist/renderer/UdsRenderer.js +1 -1
  210. package/dist/renderer/assetEntries.d.ts +37 -0
  211. package/dist/renderer/assetEntries.js +39 -0
  212. package/dist/renderer/assetRenderable.d.ts +14 -6
  213. package/dist/renderer/assetRenderable.js +8 -2
  214. package/dist/renderer/componentEntry.d.ts +58 -0
  215. package/dist/renderer/componentEntry.js +71 -0
  216. package/dist/renderer/icon-library.d.ts +20 -0
  217. package/dist/renderer/index.d.ts +7 -7
  218. package/dist/renderer/index.js +5 -5
  219. package/dist/renderer/primitives/SlotRenderer.d.ts +1 -1
  220. package/dist/renderer/primitives/SlotRenderer.js +1 -1
  221. package/dist/renderer/runtimeComponent.d.ts +33 -0
  222. package/dist/renderer/runtimeComponent.js +113 -0
  223. package/dist/renderer/spec-content.d.ts +43 -0
  224. package/dist/renderer/spec-content.js +95 -0
  225. package/dist/renderer/wrapRegistry.d.ts +22 -37
  226. package/dist/renderer/wrapRegistry.js +10 -20
  227. package/dist/renderer/wrappers/event-bridge.js +6 -5
  228. package/dist/renderer/wrappers/hex-normalize.js +6 -5
  229. package/dist/renderer/wrappers/html-aliases.d.ts +7 -2
  230. package/dist/renderer/wrappers/html-aliases.js +36 -19
  231. package/dist/renderer/wrappers/inline-styles.js +7 -6
  232. package/dist/spec/asset-jsx.d.ts +18 -5
  233. package/dist/spec/asset-jsx.js +27 -16
  234. package/dist/spec/collapse-text-labels.d.ts +5 -15
  235. package/dist/spec/collapse-text-labels.js +26 -19
  236. package/dist/spec/empty-node-slots.d.ts +56 -34
  237. package/dist/spec/empty-node-slots.js +87 -47
  238. package/dist/spec/index.d.ts +21 -10
  239. package/dist/spec/index.js +21 -10
  240. package/dist/spec/jsxToSpec.d.ts +14 -9
  241. package/dist/spec/jsxToSpec.js +20 -3
  242. package/dist/spec/preview-elements.d.ts +63 -0
  243. package/dist/spec/preview-elements.js +89 -0
  244. package/dist/spec/rewrite-refs.d.ts +58 -0
  245. package/dist/spec/rewrite-refs.js +250 -0
  246. package/dist/spec/specToJsx.js +18 -29
  247. package/dist/tsconfig.tsbuildinfo +1 -1
  248. package/package.json +39 -52
  249. package/dist/AssetGroup.d.ts +0 -77
  250. package/dist/AssetGroup.js +0 -125
  251. package/dist/Component.d.ts +0 -333
  252. package/dist/Component.js +0 -1061
  253. package/dist/ComponentGroup.d.ts +0 -22
  254. package/dist/ComponentGroup.js +0 -51
  255. package/dist/CompositeStyle.d.ts +0 -30
  256. package/dist/CompositeStyle.js +0 -59
  257. package/dist/Config.d.ts +0 -528
  258. package/dist/Config.js +0 -1712
  259. package/dist/Mode.d.ts +0 -41
  260. package/dist/Mode.js +0 -81
  261. package/dist/Modifier.d.ts +0 -51
  262. package/dist/Modifier.js +0 -97
  263. package/dist/MotionDef.d.ts +0 -81
  264. package/dist/MotionDef.js +0 -212
  265. package/dist/Props.d.ts +0 -332
  266. package/dist/Props.js +0 -35
  267. package/dist/Provider.d.ts +0 -20
  268. package/dist/Provider.js +0 -14
  269. package/dist/StyleProp.d.ts +0 -145
  270. package/dist/StyleProp.js +0 -239
  271. package/dist/Token.d.ts +0 -76
  272. package/dist/Token.js +0 -133
  273. package/dist/TokenGroup.d.ts +0 -33
  274. package/dist/TokenGroup.js +0 -69
  275. package/dist/asset-kind.d.ts +0 -55
  276. package/dist/asset-kind.js +0 -29
  277. package/dist/asset-value.d.ts +0 -23
  278. package/dist/asset-value.js +0 -40
  279. package/dist/brands.d.ts +0 -30
  280. package/dist/brands.js +0 -20
  281. package/dist/captureCallerPath.d.ts +0 -48
  282. package/dist/captureCallerPath.js +0 -95
  283. package/dist/colorExpressions.d.ts +0 -131
  284. package/dist/colorExpressions.js +0 -148
  285. package/dist/component-referrers.d.ts +0 -30
  286. package/dist/component-referrers.js +0 -40
  287. package/dist/config-diagnostics.d.ts +0 -21
  288. package/dist/config-diagnostics.js +0 -12
  289. package/dist/defineAssetGroup.d.ts +0 -200
  290. package/dist/defineAssetGroup.js +0 -323
  291. package/dist/defineProvider.d.ts +0 -29
  292. package/dist/defineProvider.js +0 -60
  293. package/dist/element-marker.d.ts +0 -63
  294. package/dist/element-marker.js +0 -113
  295. package/dist/entity-utils.d.ts +0 -56
  296. package/dist/entity-utils.js +0 -105
  297. package/dist/extension.d.ts +0 -55
  298. package/dist/extension.js +0 -214
  299. package/dist/factories.d.ts +0 -863
  300. package/dist/factories.js +0 -475
  301. package/dist/font-face.d.ts +0 -25
  302. package/dist/font-face.js +0 -72
  303. package/dist/foreign-component-name.d.ts +0 -21
  304. package/dist/foreign-component-name.js +0 -42
  305. package/dist/interpolate.d.ts +0 -20
  306. package/dist/interpolate.js +0 -10
  307. package/dist/jsx/__fixtures__/cross-component-preview.d.ts +0 -3
  308. package/dist/jsx/__fixtures__/cross-component-preview.js +0 -15
  309. package/dist/jsx/jsx-dev-runtime.d.ts +0 -15
  310. package/dist/jsx/jsx-dev-runtime.js +0 -11
  311. package/dist/jsx/jsx-runtime.d.ts +0 -48
  312. package/dist/jsx/jsx-runtime.js +0 -305
  313. package/dist/linked-system-pins.d.ts +0 -30
  314. package/dist/linked-system-pins.js +0 -45
  315. package/dist/linkedNodePrefixes.d.ts +0 -73
  316. package/dist/linkedNodePrefixes.js +0 -99
  317. package/dist/markers.d.ts +0 -235
  318. package/dist/markers.js +0 -70
  319. package/dist/motion-constants.d.ts +0 -37
  320. package/dist/motion-constants.js +0 -61
  321. package/dist/native/NativeConfig.d.ts +0 -56
  322. package/dist/native/NativeConfig.js +0 -162
  323. package/dist/native/assets.d.ts +0 -20
  324. package/dist/native/assets.js +0 -17
  325. package/dist/native/components.d.ts +0 -139
  326. package/dist/native/components.js +0 -67
  327. package/dist/native/index.d.ts +0 -18
  328. package/dist/native/index.js +0 -17
  329. package/dist/native/modes.d.ts +0 -10
  330. package/dist/native/modes.js +0 -11
  331. package/dist/native/types.d.ts +0 -95
  332. package/dist/native/types.js +0 -0
  333. package/dist/native/validation.d.ts +0 -8
  334. package/dist/native/validation.js +0 -885
  335. package/dist/native/values.d.ts +0 -7
  336. package/dist/native/values.js +0 -15
  337. package/dist/native.d.ts +0 -12
  338. package/dist/native.js +0 -10
  339. package/dist/refs.d.ts +0 -204
  340. package/dist/refs.js +0 -129
  341. package/dist/registry-aliases.d.ts +0 -33
  342. package/dist/registry-aliases.js +0 -35
  343. package/dist/registry-dir.d.ts +0 -22
  344. package/dist/registry-dir.js +0 -10
  345. package/dist/renderer/makeRegistry.d.ts +0 -34
  346. package/dist/renderer/makeRegistry.js +0 -52
  347. package/dist/renderer/makeUdsRenderer.d.ts +0 -13
  348. package/dist/renderer/makeUdsRenderer.js +0 -51
  349. package/dist/renderer/wrappers/component-slots.d.ts +0 -41
  350. package/dist/renderer/wrappers/component-slots.js +0 -66
  351. package/dist/renderer/wrappers/slot-resolution.d.ts +0 -25
  352. package/dist/renderer/wrappers/slot-resolution.js +0 -68
  353. package/dist/renderer/wrappers/void-elements.d.ts +0 -37
  354. package/dist/renderer/wrappers/void-elements.js +0 -40
  355. package/dist/spec/layer-props.d.ts +0 -52
  356. package/dist/spec/layer-props.js +0 -149
  357. package/dist/spec/preview-controls.d.ts +0 -44
  358. package/dist/spec/preview-controls.js +0 -139
  359. package/dist/spec/slot-refs.d.ts +0 -39
  360. package/dist/spec/slot-refs.js +0 -56
  361. package/dist/token-override-rows.d.ts +0 -68
  362. package/dist/token-override-rows.js +0 -233
  363. package/dist/tokenValueType.d.ts +0 -34
  364. package/dist/tokenValueType.js +0 -138
  365. package/dist/types/css-properties.d.ts +0 -233
  366. package/dist/types/css-properties.js +0 -14
  367. package/dist/types/css-property-keywords.d.ts +0 -156
  368. package/dist/types/css-property-keywords.js +0 -616
  369. package/dist/types/css-values.d.ts +0 -63
  370. package/dist/types/css-values.js +0 -16
  371. package/dist/types.d.ts +0 -1138
  372. package/dist/types.js +0 -12
  373. package/dist/units.d.ts +0 -14
  374. package/dist/units.js +0 -16
  375. package/dist/utils/index.d.ts +0 -4
  376. package/dist/utils/index.js +0 -4
@@ -0,0 +1,196 @@
1
+ import { RebaseConflict, StampedPatch } from "./overlay.js";
2
+ import { Snapshot } from "./snapshot.js";
3
+ import { Config, LiveConfig, Patch } from "./Config.js";
4
+ import { ChangeHookMap, ChangeHookRun } from "./change-hooks.js";
5
+ import { WalkOptions } from "./schema-version.js";
6
+
7
+ //#region src/framework/session.d.ts
8
+ /** Confirmed state arriving from storage. */
9
+ interface SourceChange {
10
+ readonly snapshot: Snapshot;
11
+ /** Ids of local patches the new base has absorbed — a merge landing. They are dropped rather than
12
+ * replayed, because the base already contains them. Empty for an ordinary change. */
13
+ readonly merged?: readonly string[];
14
+ }
15
+ /** Where confirmed state comes from — the base half of a branch: the artifact a push built. */
16
+ interface ConfigSource {
17
+ snapshot(): Snapshot;
18
+ subscribe(onChange: (change: SourceChange) => void): () => void;
19
+ }
20
+ /**
21
+ * Where the shared draft lives — the branch's unmerged patches, as one ordered list.
22
+ *
23
+ * Separate from {@link ConfigSource} because a branch's two sources carry different things: the
24
+ * base is entity state, the draft is patches. Only the draft is collaborative, so only the draft
25
+ * has a live shared home (the `system:{branchId}` Y.Doc); the base is the same artifact for every
26
+ * client and moves only when a push publishes a new one.
27
+ *
28
+ * The unit is the whole list, not a delta. A client adopts it rather than reconciling entry by
29
+ * entry, which is what makes its own echo a no-op — see `Config.withDraft`.
30
+ */
31
+ interface DraftSource {
32
+ draft(): readonly StampedPatch[];
33
+ subscribe(onChange: (draft: readonly StampedPatch[]) => void): () => void;
34
+ }
35
+ /** Where local patches go. Called once per batch, never once per patch. */
36
+ interface ConfigSink {
37
+ commit(patches: readonly StampedPatch[], next: Config): void | Promise<void>;
38
+ /**
39
+ * Take named entries back out of the shared draft — the other half of the pair, and the reason it
40
+ * has to exist: `drop(ids)` filters a list only this client can see. A patch that had already
41
+ * reached the sink therefore came back on the next change notification, because a client adopts the
42
+ * shared list wholesale (see {@link DraftSource}). Wholesale adoption is what makes an echo a no-op
43
+ * and what resurrects a removal that was never written through.
44
+ *
45
+ * ID-addressed on purpose. It names its victims, so an incomplete local view can't delete anything
46
+ * it didn't mean to. It must never grow into a `replaceAll(desiredList)` convenience: a full set
47
+ * deletes by omission, and omission is indistinguishable from ignorance.
48
+ *
49
+ * Optional, so a sink that only appends — a test double, a local-only session — still satisfies
50
+ * this. `drop` then stays local, exactly as it behaved before this existed.
51
+ *
52
+ * Must be idempotent, like `commit`: retracting an absent id is a no-op, so a retry and two clients
53
+ * retracting the same id both converge.
54
+ */
55
+ retract?(patchIds: readonly string[]): void | Promise<void>;
56
+ }
57
+ /** Which replay dropped an edit — see {@link ConfigSessionOptions.onConflict}. */
58
+ type RebaseOrigin = 'base' | 'draft' | 'drop';
59
+ interface ConfigSessionOptions {
60
+ source?: ConfigSource;
61
+ /** The shared draft. Without one, the pending patches are this client's alone. */
62
+ draft?: DraftSource;
63
+ sink?: ConfigSink;
64
+ /**
65
+ * When to hand the pending patches to the sink — transport, not meaning: how many Yjs updates a
66
+ * burst of edits produces, never which edits belong together (that is the changeset on the stamp).
67
+ * `'microtask'` (the default) flushes at the end of the current synchronous run, which is exactly
68
+ * the boundary a loop of `apply` calls sits inside. A number debounces by that many ms; `'manual'`
69
+ * flushes only on `flush()`.
70
+ */
71
+ flush?: 'microtask' | 'manual' | number;
72
+ /**
73
+ * Called when a rebase drops local edits the new base invalidated — never silent, because the
74
+ * user made those edits and they are now gone.
75
+ *
76
+ * `origin` says which replay dropped them, because the honest wording differs: `'base'` is a real
77
+ * base move (the classic rebase), `'draft'` is the shared draft being re-adopted onto the same
78
+ * base (a stale or conflicting entry another writer left there), `'drop'` is a dependent casualty
79
+ * of an explicit drop. Each casualty is announced once per life in the draft — the draft replays
80
+ * wholesale on every change, and re-announcing the same doomed entry on every keystroke taught
81
+ * users to ignore the report. A base move resets that memory: the same edit dying on a new base
82
+ * is news.
83
+ */
84
+ onConflict?: (dropped: RebaseConflict[], origin: RebaseOrigin) => void;
85
+ /**
86
+ * Called ONCE when the shared draft carries a patch stamped at a NEWER schema version than this
87
+ * build understands — a collaborator on a newer deploy is writing to the same draft. The entry is
88
+ * held out of replay (applying it would fail as a bogus conflict) and never retracted (it is valid
89
+ * for newer readers); the only real remedy is reloading onto the newer deploy, so the surface
90
+ * should say exactly that.
91
+ */
92
+ onSchemaAhead?: () => void;
93
+ /** Test seam — the adopt-side patch upgrade walks this chain/target instead of the registered
94
+ * ones. Real sessions omit it: registration refuses synthetic versions, so only a seam can
95
+ * exercise the walk. */
96
+ schemaWalk?: WalkOptions;
97
+ /** Called when a hooked derivation throws. The surface waiting on that value is now showing
98
+ * something stale, and only the caller can say so. */
99
+ onDeriveError?: (name: string, error: unknown) => void;
100
+ /**
101
+ * Run over every value this session advances to, before anyone reads it — for content that is a
102
+ * FUNCTION of the config rather than an edit to it.
103
+ *
104
+ * The rows a linked system brings are the case: a link declares a pin and the rows are derived from
105
+ * it, so a surface reading the session has to see them, and a replay has to make them again. Purely
106
+ * derived content only. It must add no patches and must not depend on the value it produced, since
107
+ * it runs on the result of every apply, rebase, adopt and drop.
108
+ *
109
+ * The BASE is untouched, which is what makes it safe to run here: a rebase and a drop both replay
110
+ * from the base, so derived content is rebuilt rather than replayed as though someone had written
111
+ * it.
112
+ */
113
+ derive?: <C extends Config>(config: C) => C;
114
+ }
115
+ declare class ConfigSession<C extends Config = Config> implements LiveConfig {
116
+ #private;
117
+ constructor(config: C, options?: ConfigSessionOptions);
118
+ get config(): C;
119
+ /** Apply a patch and advance to the result. Returns the new config for a caller that wants it
120
+ * immediately; `session.config` is the same value. */
121
+ apply(patch: Patch, id?: string): C;
122
+ /**
123
+ * Move the session forward to a config someone else derived from this one.
124
+ *
125
+ * The seam for code that is handed the next config rather than producing it — an operation
126
+ * handler is `config.apply(...)`, so what it returns is the next value and the session takes it
127
+ * rather than re-deriving it. Patches the value gained on the way join the outgoing batch, so a
128
+ * mutation made this way persists like any other.
129
+ */
130
+ advance(next: C): C;
131
+ /**
132
+ * Attach one resolved source and advance to the result.
133
+ *
134
+ * A read arriving late, not an edit: the answer is applied to the value the session holds now, so a
135
+ * caller never hands over a config it read a round trip ago, and nothing joins the outgoing batch.
136
+ * The edition is unchanged, which is what lets a write in flight tell this apart from a peer's edit.
137
+ */
138
+ attach(resolution: Parameters<C['attachResolved']>[0]): C;
139
+ subscribe(listener: () => void): () => void;
140
+ /**
141
+ * Derive something from this config for as long as the caller is interested.
142
+ *
143
+ * A surface hooks what it is showing, which is the point: today a provider subscribes once and
144
+ * every tab pays for the derive whether a canvas is mounted or not. The returned disposer is the
145
+ * whole teardown, so the effect that registers it is one line — and the same point can be hooked
146
+ * twice under different names, so an inspector can watch timings without displacing the paint.
147
+ *
148
+ * ```ts
149
+ * useEffect(() => session.hook('onChange', { styles: stylesOnChange }).dispose, [session]);
150
+ * ```
151
+ *
152
+ * The lifecycle is named rather than implied so the other points (`onLoad`, `onBuild`, `onPush`)
153
+ * land beside it instead of each inventing a method.
154
+ */
155
+ hook<H extends ChangeHookMap<C>>(_lifecycle: 'onChange', hooks: H): ChangeHookRun<H>;
156
+ /**
157
+ * The current value of a hooked derivation, by name — the pull half of `hook`.
158
+ *
159
+ * Typed by the caller, because a session accumulates hook sets from surfaces it knows nothing
160
+ * about; the statically-typed read is the `derived` on what `hook` returned. Two hook sets may
161
+ * claim one name — an inspector watching the same point — and this answers with the first that has
162
+ * answered, so a reader that cares which is reading the wrong way round.
163
+ */
164
+ derived<T>(name: string): T | undefined;
165
+ /** Replay the local edits onto a new base. Dropped edits go to `onConflict`. */
166
+ rebase(base: Snapshot): void;
167
+ /** Drop local edits a merge folded into the base. */
168
+ merge(patchIds: readonly string[]): void;
169
+ /**
170
+ * Drop local edits the server refused — the opposite of `merge`, and the difference matters.
171
+ *
172
+ * A merged patch leaves its value readable, because the new base contains it. A refused one must
173
+ * take its effect with it: the edit was not saved, so a surface that kept showing it would be
174
+ * lying about what the branch holds. That is why this replays the remaining draft instead of
175
+ * filtering the pending list — a later patch built on the refused one has nothing to build on, and
176
+ * the replay is what surfaces that rather than leaving a half-applied overlay.
177
+ *
178
+ * Those dependent casualties go to `onConflict` like any other dropped edit. Unknown ids change
179
+ * nothing, so a retry that already settled is harmless.
180
+ *
181
+ * The removal is also written through to the sink, for every dropped patch that had reached it. It
182
+ * has to be: the pending list this filters is local, and a client adopts the shared draft wholesale,
183
+ * so a patch removed only here reappears on the next change notification — any peer edit, any
184
+ * reconnect. See {@link ConfigSink.retract}.
185
+ */
186
+ drop(patchIds: readonly string[]): void;
187
+ /** Hand the pending batch to the sink now. Resolves once the sink has taken it. */
188
+ flush(): Promise<void>;
189
+ /** Detach from the source. The config value remains readable — it just stops advancing. */
190
+ dispose(): void;
191
+ }
192
+ /** A source over a fixed snapshot that never changes — the in-memory case, and what
193
+ * `MapItemStore` used to be. Useful as a seed and in tests. */
194
+ declare function memoryConfigSource(snapshot: Snapshot): ConfigSource;
195
+ //#endregion
196
+ export { ConfigSession, ConfigSessionOptions, ConfigSink, ConfigSource, DraftSource, RebaseOrigin, SourceChange, memoryConfigSource };
@@ -0,0 +1,367 @@
1
+ import { upgradeDraftEntries } from "./schema-version.js";
2
+ import { runChangeHooks } from "./change-hooks.js";
3
+ //#region src/framework/session.ts
4
+ /**
5
+ * Whether two patch lists are the same draft — same entries, same order.
6
+ *
7
+ * By ID, because an entry is immutable once stamped: the id is minted with the patch and travels with
8
+ * it through the sink, the shared list and the storage row, so two entries sharing one are the same
9
+ * edit however many times it was serialized on the way round.
10
+ */
11
+ function sameDraft(a, b) {
12
+ return a.length === b.length && a.every((entry, i) => entry.id === b[i]?.id);
13
+ }
14
+ /**
15
+ * One entry per id, first occurrence kept, order otherwise preserved.
16
+ *
17
+ * A shared draft can carry the same stamped patch twice: two realtime instances that each seeded a
18
+ * cold room from the same rows mint their own Yjs items for one id, and the merge keeps both. The
19
+ * second copy is the same edit, so it can't be replayed — a `create` fails as `ALREADY_EXISTS` — and
20
+ * reporting it would tell the user an edit was lost when nothing was.
21
+ */
22
+ function uniqueById(entries) {
23
+ const seen = /* @__PURE__ */ new Set();
24
+ const unique = [];
25
+ for (const entry of entries) {
26
+ if (seen.has(entry.id)) continue;
27
+ seen.add(entry.id);
28
+ unique.push(entry);
29
+ }
30
+ return unique;
31
+ }
32
+ var ConfigSession = class {
33
+ #config;
34
+ #listeners = /* @__PURE__ */ new Set();
35
+ #options;
36
+ /** Applied locally but not yet handed to the sink. Distinct from `config.pending`, which stays
37
+ * populated until the source confirms — sent and confirmed are different questions. */
38
+ #uncommitted = [];
39
+ /** Casualties already reported to `onConflict`, by id — the once-per-life memory. Pruned to the
40
+ * shared draft on every adopt, cleared whenever the base actually moves. */
41
+ #announcedDrops = /* @__PURE__ */ new Set();
42
+ /** Whether {@link ConfigSessionOptions.onSchemaAhead} already fired — the draft re-adopts
43
+ * wholesale on every change, and one reload prompt is the message. */
44
+ #schemaAheadAnnounced = false;
45
+ /**
46
+ * Patches handed to the sink whose `commit` hasn't resolved yet, in apply order.
47
+ *
48
+ * They are in neither `#uncommitted` (flush empties it before awaiting) nor durably shared, and that
49
+ * gap matters twice:
50
+ *
51
+ * - for `drop`, retracting one now would find nothing to remove, and the append would land
52
+ * afterwards — resurrecting the patch permanently. So a drop naming one of these defers.
53
+ * - for {@link #adoptDraft}, which rebuilds the pending list from the shared draft plus whatever
54
+ * is only local. A patch in neither list is not dropped, it simply stops existing — no replay,
55
+ * no conflict, no report. That erased "add a token, then delete it while the add is still
56
+ * committing", leaving the delete to replay alone and fail as `NOT_FOUND`.
57
+ *
58
+ * So this holds the entries rather than their ids: the adopt needs the patch to replay and the
59
+ * position to replay it at, and a `Set<string>` had neither.
60
+ */
61
+ #inFlight = [];
62
+ /** Ids dropped while their own commit was in flight — retracted once it settles. */
63
+ #retractOnSettle = /* @__PURE__ */ new Set();
64
+ /** Derivations hooked onto this session — held so `dispose` takes them down with it. */
65
+ #runs = /* @__PURE__ */ new Set();
66
+ #scheduled = false;
67
+ #timer;
68
+ #unsubscribe;
69
+ #unsubscribeDraft;
70
+ constructor(config, options = {}) {
71
+ this.#options = options;
72
+ this.#config = this.#derived(config.withOwner(this));
73
+ if (options.source) {
74
+ this.#config = this.#afterRebase(this.#config.rebase(options.source.snapshot()), "base");
75
+ this.#unsubscribe = options.source.subscribe((change) => this.#onSourceChange(change));
76
+ }
77
+ if (options.draft) {
78
+ this.#adoptDraft(options.draft.draft());
79
+ this.#unsubscribeDraft = options.draft.subscribe((draft) => {
80
+ if (this.#adoptDraft(draft)) this.#notify();
81
+ });
82
+ }
83
+ }
84
+ get config() {
85
+ return this.#config;
86
+ }
87
+ /** Apply a patch and advance to the result. Returns the new config for a caller that wants it
88
+ * immediately; `session.config` is the same value. */
89
+ apply(patch, id) {
90
+ return this.advance(this.#config.apply(patch, id));
91
+ }
92
+ /**
93
+ * Move the session forward to a config someone else derived from this one.
94
+ *
95
+ * The seam for code that is handed the next config rather than producing it — an operation
96
+ * handler is `config.apply(...)`, so what it returns is the next value and the session takes it
97
+ * rather than re-deriving it. Patches the value gained on the way join the outgoing batch, so a
98
+ * mutation made this way persists like any other.
99
+ */
100
+ advance(next) {
101
+ if (next === this.#config) return next;
102
+ const before = this.#config;
103
+ this.#config = this.#derived(next);
104
+ if (next.pending.length > before.pending.length) {
105
+ this.#uncommitted.push(...next.pending.slice(before.pending.length));
106
+ this.#schedule();
107
+ }
108
+ this.#notify();
109
+ return this.#config;
110
+ }
111
+ /**
112
+ * Attach one resolved source and advance to the result.
113
+ *
114
+ * A read arriving late, not an edit: the answer is applied to the value the session holds now, so a
115
+ * caller never hands over a config it read a round trip ago, and nothing joins the outgoing batch.
116
+ * The edition is unchanged, which is what lets a write in flight tell this apart from a peer's edit.
117
+ */
118
+ attach(resolution) {
119
+ this.#config = this.#derived(this.#config.attachResolved(resolution));
120
+ this.#notify();
121
+ return this.#config;
122
+ }
123
+ subscribe(listener) {
124
+ this.#listeners.add(listener);
125
+ return () => this.#listeners.delete(listener);
126
+ }
127
+ /**
128
+ * Derive something from this config for as long as the caller is interested.
129
+ *
130
+ * A surface hooks what it is showing, which is the point: today a provider subscribes once and
131
+ * every tab pays for the derive whether a canvas is mounted or not. The returned disposer is the
132
+ * whole teardown, so the effect that registers it is one line — and the same point can be hooked
133
+ * twice under different names, so an inspector can watch timings without displacing the paint.
134
+ *
135
+ * ```ts
136
+ * useEffect(() => session.hook('onChange', { styles: stylesOnChange }).dispose, [session]);
137
+ * ```
138
+ *
139
+ * The lifecycle is named rather than implied so the other points (`onLoad`, `onBuild`, `onPush`)
140
+ * land beside it instead of each inventing a method.
141
+ */
142
+ hook(_lifecycle, hooks) {
143
+ const run = runChangeHooks({
144
+ feed: {
145
+ current: () => this.#config,
146
+ subscribe: (listener) => this.subscribe(listener)
147
+ },
148
+ hooks,
149
+ onError: this.#options.onDeriveError
150
+ });
151
+ const registered = {
152
+ ...run,
153
+ dispose: () => {
154
+ this.#runs.delete(registered);
155
+ run.dispose();
156
+ }
157
+ };
158
+ this.#runs.add(registered);
159
+ return registered;
160
+ }
161
+ /**
162
+ * The current value of a hooked derivation, by name — the pull half of `hook`.
163
+ *
164
+ * Typed by the caller, because a session accumulates hook sets from surfaces it knows nothing
165
+ * about; the statically-typed read is the `derived` on what `hook` returned. Two hook sets may
166
+ * claim one name — an inspector watching the same point — and this answers with the first that has
167
+ * answered, so a reader that cares which is reading the wrong way round.
168
+ */
169
+ derived(name) {
170
+ for (const run of this.#runs) {
171
+ const value = run.derived(name);
172
+ if (value !== void 0) return value;
173
+ }
174
+ }
175
+ /** Replay the local edits onto a new base. Dropped edits go to `onConflict`. */
176
+ rebase(base) {
177
+ this.#config = this.#afterRebase(this.#config.rebase(base), "base");
178
+ this.#notify();
179
+ }
180
+ /** Drop local edits a merge folded into the base. */
181
+ merge(patchIds) {
182
+ const next = this.#config.merge(patchIds);
183
+ if (next === this.#config) return;
184
+ this.#config = this.#derived(next);
185
+ this.#notify();
186
+ }
187
+ /**
188
+ * Drop local edits the server refused — the opposite of `merge`, and the difference matters.
189
+ *
190
+ * A merged patch leaves its value readable, because the new base contains it. A refused one must
191
+ * take its effect with it: the edit was not saved, so a surface that kept showing it would be
192
+ * lying about what the branch holds. That is why this replays the remaining draft instead of
193
+ * filtering the pending list — a later patch built on the refused one has nothing to build on, and
194
+ * the replay is what surfaces that rather than leaving a half-applied overlay.
195
+ *
196
+ * Those dependent casualties go to `onConflict` like any other dropped edit. Unknown ids change
197
+ * nothing, so a retry that already settled is harmless.
198
+ *
199
+ * The removal is also written through to the sink, for every dropped patch that had reached it. It
200
+ * has to be: the pending list this filters is local, and a client adopts the shared draft wholesale,
201
+ * so a patch removed only here reappears on the next change notification — any peer edit, any
202
+ * reconnect. See {@link ConfigSink.retract}.
203
+ */
204
+ drop(patchIds) {
205
+ if (patchIds.length === 0) return;
206
+ const refused = new Set(patchIds);
207
+ const before = this.#config.pending;
208
+ const kept = before.filter((entry) => !refused.has(entry.id) && !(entry.changeset !== void 0 && refused.has(entry.changeset)));
209
+ if (kept.length === before.length) return;
210
+ const neverSent = new Set(this.#uncommitted.map((entry) => entry.id));
211
+ this.#uncommitted = this.#uncommitted.filter((entry) => !refused.has(entry.id));
212
+ this.#config = this.#afterRebase(this.#config.withDraft(kept), "drop");
213
+ this.#notify();
214
+ const survived = new Set(this.#config.pending.map((entry) => entry.id));
215
+ this.#retract(before.map((entry) => entry.id).filter((id) => !survived.has(id) && !neverSent.has(id)));
216
+ }
217
+ /** Write a removal through to the sink, deferring any id whose own commit is still in flight. */
218
+ #retract(ids) {
219
+ if (ids.length === 0) return;
220
+ const inFlight = new Set(this.#inFlight.map((entry) => entry.id));
221
+ const now = [];
222
+ for (const id of ids) if (inFlight.has(id)) this.#retractOnSettle.add(id);
223
+ else now.push(id);
224
+ this.#sendRetraction(now);
225
+ }
226
+ /**
227
+ * Hand ids to the sink's `retract`, if it has one.
228
+ *
229
+ * A sink without it keeps `drop` local-only — the behaviour before the pair existed — rather than
230
+ * throwing. A rejected retraction goes back on the deferred set so the next settle retries it:
231
+ * losing one strands the row in the shared draft, which is the resurrection this exists to stop.
232
+ * Deliberately not a background retry loop — that is a client-side sweeper, and a sweeper is where
233
+ * full-set pushes come from.
234
+ */
235
+ #sendRetraction(ids) {
236
+ if (ids.length === 0) return;
237
+ const retract = this.#options.sink?.retract;
238
+ if (!retract) return;
239
+ try {
240
+ const settled = retract.call(this.#options.sink, ids);
241
+ if (settled) settled.catch(() => {
242
+ for (const id of ids) this.#retractOnSettle.add(id);
243
+ });
244
+ } catch {
245
+ for (const id of ids) this.#retractOnSettle.add(id);
246
+ }
247
+ }
248
+ /** Hand the pending batch to the sink now. Resolves once the sink has taken it. */
249
+ async flush() {
250
+ this.#scheduled = false;
251
+ if (this.#timer !== void 0) {
252
+ clearTimeout(this.#timer);
253
+ this.#timer = void 0;
254
+ }
255
+ const batch = this.#uncommitted;
256
+ if (batch.length === 0 || !this.#options.sink) return;
257
+ this.#uncommitted = [];
258
+ this.#inFlight = [...this.#inFlight, ...batch];
259
+ try {
260
+ await this.#options.sink.commit(batch, this.#config);
261
+ } catch (error) {
262
+ this.#uncommitted = [...batch.filter((entry) => !this.#retractOnSettle.has(entry.id)), ...this.#uncommitted];
263
+ throw error;
264
+ } finally {
265
+ const settled = new Set(batch.map((entry) => entry.id));
266
+ this.#inFlight = this.#inFlight.filter((entry) => !settled.has(entry.id));
267
+ const deferred = [...this.#retractOnSettle];
268
+ this.#retractOnSettle.clear();
269
+ this.#sendRetraction(deferred);
270
+ }
271
+ }
272
+ /** Detach from the source. The config value remains readable — it just stops advancing. */
273
+ dispose() {
274
+ this.#unsubscribe?.();
275
+ this.#unsubscribe = void 0;
276
+ this.#unsubscribeDraft?.();
277
+ this.#unsubscribeDraft = void 0;
278
+ for (const run of [...this.#runs]) run.dispose();
279
+ this.#listeners.clear();
280
+ if (this.#timer !== void 0) clearTimeout(this.#timer);
281
+ }
282
+ /**
283
+ * Take the shared draft as the pending list. Answers whether the config moved, so a change that
284
+ * says nothing new doesn't advance.
285
+ *
286
+ * Local patches that haven't reached the shared list yet are kept on the end. A batch applied a
287
+ * microtask ago is ours and still uncommitted, and adopting a list that predates it would undo
288
+ * the user's last edit the moment a collaborator typed.
289
+ *
290
+ * "Local" is in flight plus uncommitted, in that order, because those are the two ways a patch can
291
+ * be ours and not yet shared — and a patch in neither list doesn't get reported as dropped, it just
292
+ * stops existing. Reading only `#uncommitted` erased the whole in-flight batch: add a token, delete
293
+ * it before the add's commit settles, and the add vanished while the delete replayed alone onto a
294
+ * base that never had the token, dying as `NOT_FOUND`. In flight comes first because it was applied
295
+ * first — `flush` takes the head of the queue.
296
+ *
297
+ * An in-flight entry a `drop` already claimed is left out: `drop` filters `#uncommitted` itself but
298
+ * cannot touch a sent batch (it defers the retraction to `#retractOnSettle`), so without this the
299
+ * adopt would read the dropped patch straight back.
300
+ */
301
+ #adoptDraft(draft) {
302
+ const { entries: readable, held } = upgradeDraftEntries(draft, this.#options.schemaWalk);
303
+ if (held.length > 0 && !this.#schemaAheadAnnounced) {
304
+ this.#schemaAheadAnnounced = true;
305
+ this.#options.onSchemaAhead?.();
306
+ }
307
+ const shared = new Set(readable.map((entry) => entry.id));
308
+ const localOnly = [...this.#inFlight, ...this.#uncommitted].filter((entry) => !shared.has(entry.id) && !this.#retractOnSettle.has(entry.id));
309
+ for (const id of this.#announcedDrops) if (!shared.has(id)) this.#announcedDrops.delete(id);
310
+ const adopted = uniqueById([...readable, ...localOnly]);
311
+ if (sameDraft(adopted, this.#config.pending)) return false;
312
+ this.#config = this.#afterRebase(this.#config.withDraft(adopted), "draft");
313
+ return true;
314
+ }
315
+ #onSourceChange(change) {
316
+ let next = this.#config;
317
+ if (change.merged?.length) next = next.merge(change.merged);
318
+ this.#config = this.#afterRebase(next.rebase(change.snapshot), "base");
319
+ this.#notify();
320
+ }
321
+ /**
322
+ * Unwrap a rebase result, reporting whatever it had to drop — a local edit the new base
323
+ * invalidated is the user's work disappearing, so it is never silent.
324
+ *
325
+ * Announced once per casualty: the shared draft replays wholesale on every change, so without a
326
+ * memory the same doomed entry re-reported on every subsequent edit, its own echo included — an
327
+ * alarm that fires forever is an alarm nobody reads. A base move clears the memory (the same edit
328
+ * dying on a new base is news); an id that leaves the draft is forgotten in {@link #adoptDraft}.
329
+ */
330
+ #afterRebase({ config, dropped }, origin) {
331
+ if (origin === "base") this.#announcedDrops.clear();
332
+ const news = dropped.filter((entry) => !this.#announcedDrops.has(entry.id));
333
+ for (const entry of news) this.#announcedDrops.add(entry.id);
334
+ if (news.length) this.#options.onConflict?.(news, origin);
335
+ return this.#derived(config);
336
+ }
337
+ /** One value, ready to be read — {@link ConfigSessionOptions.derive} applied, or the value itself
338
+ * when the session was given none. Every assignment to `#config` goes through here. */
339
+ #derived(config) {
340
+ return this.#options.derive ? this.#options.derive(config) : config;
341
+ }
342
+ #schedule() {
343
+ const { flush = "microtask", sink } = this.#options;
344
+ if (!sink || flush === "manual" || this.#scheduled) return;
345
+ this.#scheduled = true;
346
+ if (flush === "microtask") {
347
+ queueMicrotask(() => {
348
+ if (this.#scheduled) this.flush();
349
+ });
350
+ return;
351
+ }
352
+ this.#timer = setTimeout(() => void this.flush(), flush);
353
+ }
354
+ #notify() {
355
+ for (const listener of this.#listeners) listener();
356
+ }
357
+ };
358
+ /** A source over a fixed snapshot that never changes — the in-memory case, and what
359
+ * `MapItemStore` used to be. Useful as a seed and in tests. */
360
+ function memoryConfigSource(snapshot) {
361
+ return {
362
+ snapshot: () => snapshot,
363
+ subscribe: () => () => {}
364
+ };
365
+ }
366
+ //#endregion
367
+ export { ConfigSession, memoryConfigSource };
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/framework/signature.d.ts
4
+ interface SignatureOptions {
5
+ /** Object nesting rendered in full before an object collapses to `{…}`. */
6
+ readonly depth?: number;
7
+ /** Enum members listed before the rest collapse to `…`. */
8
+ readonly enumLimit?: number;
9
+ }
10
+ /** Render one JSON Schema node as a type signature. `root` holds `$defs` for `$ref` lookups. */
11
+ declare function renderSignature(schema: unknown, options?: SignatureOptions): string;
12
+ /** The signature of a zod schema, read from its input side since it documents what an author writes. */
13
+ declare function signatureOf(schema: z.ZodTypeAny | undefined, options?: SignatureOptions): string | undefined;
14
+ //#endregion
15
+ export { SignatureOptions, renderSignature, signatureOf };
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ //#region src/framework/signature.ts
3
+ /**
4
+ * A JSON Schema rendered as a one-line type signature — `{ value: string | ref<token>, type?: string }`.
5
+ *
6
+ * The full JSON Schema for one create runs to tens of kilobytes; the signature says the same shape in
7
+ * a line a prompt can afford to carry for every kind, so a model learns what an entity takes before
8
+ * it writes one. Depth is capped: an object nested past it prints as `{…}`, and the full schema is one
9
+ * `address` describe away. Two levels is enough to show a kind's fields and the shape of each field's
10
+ * members; a token's color operation is recursive and would otherwise unfold without end.
11
+ */
12
+ const DEFAULT_DEPTH = 2;
13
+ const DEFAULT_ENUM_LIMIT = 8;
14
+ function isNode(value) {
15
+ return typeof value === "object" && value !== null;
16
+ }
17
+ function literal(value) {
18
+ return typeof value === "string" ? `'${value}'` : String(value);
19
+ }
20
+ function unique(parts) {
21
+ return [...new Set(parts)];
22
+ }
23
+ /** Render one JSON Schema node as a type signature. `root` holds `$defs` for `$ref` lookups. */
24
+ function renderSignature(schema, options = {}) {
25
+ if (!isNode(schema)) return "unknown";
26
+ const depth = options.depth ?? DEFAULT_DEPTH;
27
+ const enumLimit = options.enumLimit ?? DEFAULT_ENUM_LIMIT;
28
+ const render = (node, remaining) => {
29
+ if (node.$ref) {
30
+ const name = node.$ref.replace("#/$defs/", "");
31
+ const target = schema.$defs?.[name];
32
+ return target ? render(target, remaining) : "…";
33
+ }
34
+ if (node.ref && node.refKind) return `ref<${node.refKind}>`;
35
+ if (node.const !== void 0) return literal(node.const);
36
+ if (node.enum) {
37
+ const shown = node.enum.slice(0, enumLimit).map(literal);
38
+ if (node.enum.length > enumLimit) shown.push("…");
39
+ return shown.join(" | ");
40
+ }
41
+ const variants = node.anyOf ?? node.oneOf;
42
+ if (variants) return unique(variants.map((member) => render(member, remaining))).join(" | ");
43
+ if (node.allOf) return unique(node.allOf.map((member) => render(member, remaining))).join(" & ");
44
+ if (Array.isArray(node.type)) return unique(node.type.map((type) => render({
45
+ ...node,
46
+ type
47
+ }, remaining))).join(" | ");
48
+ if (node.type === "array") {
49
+ const item = node.items ? render(node.items, remaining) : "unknown";
50
+ return item.includes(" | ") ? `(${item})[]` : `${item}[]`;
51
+ }
52
+ if (node.type === "object" || node.properties || node.additionalProperties) return renderObject(node, remaining);
53
+ return typeof node.type === "string" ? node.type : "unknown";
54
+ };
55
+ const renderObject = (node, remaining) => {
56
+ const entries = Object.entries(node.properties ?? {});
57
+ if (entries.length === 1 && entries[0]?.[0] === "__ref") return "ref";
58
+ if (entries.length === 0) {
59
+ if (isNode(node.additionalProperties)) {
60
+ if (remaining <= 0) return "Record<string, {…}>";
61
+ return `Record<string, ${render(node.additionalProperties, remaining - 1)}>`;
62
+ }
63
+ return node.additionalProperties === false ? "{}" : "Record<string, unknown>";
64
+ }
65
+ if (remaining <= 0) return "{…}";
66
+ const required = new Set(node.required ?? []);
67
+ return `{ ${entries.map(([key, value]) => {
68
+ return `${key}${required.has(key) ? "" : "?"}: ${render(value, remaining - 1)}`;
69
+ }).join(", ")} }`;
70
+ };
71
+ return render(schema, depth);
72
+ }
73
+ /** The signature of a zod schema, read from its input side since it documents what an author writes. */
74
+ function signatureOf(schema, options) {
75
+ if (!schema) return void 0;
76
+ try {
77
+ return renderSignature(z.toJSONSchema(schema, { io: "input" }), options);
78
+ } catch {
79
+ return;
80
+ }
81
+ }
82
+ //#endregion
83
+ export { renderSignature, signatureOf };