@opetope/runtime 0.1.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 (314) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +345 -0
  4. package/README.ru.md +344 -0
  5. package/dist/application-compiler-edges.d.ts +3 -0
  6. package/dist/application-compiler-edges.js +2 -0
  7. package/dist/application-compiler-edges.js.map +1 -0
  8. package/dist/application-compiler-graph.d.ts +8 -0
  9. package/dist/application-compiler-graph.js +2 -0
  10. package/dist/application-compiler-graph.js.map +1 -0
  11. package/dist/application-compiler.d.ts +116 -0
  12. package/dist/application-compiler.js +2 -0
  13. package/dist/application-compiler.js.map +1 -0
  14. package/dist/application-conditions.d.ts +18 -0
  15. package/dist/application-conditions.js +2 -0
  16. package/dist/application-conditions.js.map +1 -0
  17. package/dist/application-definition.d.ts +30 -0
  18. package/dist/application-definition.js +2 -0
  19. package/dist/application-definition.js.map +1 -0
  20. package/dist/application-error.d.ts +10 -0
  21. package/dist/application-error.js +2 -0
  22. package/dist/application-error.js.map +1 -0
  23. package/dist/application-execution.d.ts +43 -0
  24. package/dist/application-execution.js +2 -0
  25. package/dist/application-execution.js.map +1 -0
  26. package/dist/application-feature-bindings.d.ts +11 -0
  27. package/dist/application-feature-bindings.js +2 -0
  28. package/dist/application-feature-bindings.js.map +1 -0
  29. package/dist/application-feature-instance.d.ts +5 -0
  30. package/dist/application-feature-instance.js +2 -0
  31. package/dist/application-feature-instance.js.map +1 -0
  32. package/dist/application-group-order.d.ts +29 -0
  33. package/dist/application-group-order.js +2 -0
  34. package/dist/application-group-order.js.map +1 -0
  35. package/dist/application-instance-retirement.d.ts +36 -0
  36. package/dist/application-instance-retirement.js +2 -0
  37. package/dist/application-instance-retirement.js.map +1 -0
  38. package/dist/application-open-options.d.ts +29 -0
  39. package/dist/application-open-options.js +2 -0
  40. package/dist/application-open-options.js.map +1 -0
  41. package/dist/application-port-compiler.d.ts +28 -0
  42. package/dist/application-port-compiler.js +2 -0
  43. package/dist/application-port-compiler.js.map +1 -0
  44. package/dist/attachment-call-declaration.d.ts +43 -0
  45. package/dist/attachment-call-declaration.js +2 -0
  46. package/dist/attachment-call-declaration.js.map +1 -0
  47. package/dist/attachment-declaration.d.ts +68 -0
  48. package/dist/attachment-declaration.js +2 -0
  49. package/dist/attachment-declaration.js.map +1 -0
  50. package/dist/attachment-execution.d.ts +9 -0
  51. package/dist/attachment-execution.js +2 -0
  52. package/dist/attachment-execution.js.map +1 -0
  53. package/dist/attachment-retirement-scheduler.d.ts +14 -0
  54. package/dist/attachment-retirement-scheduler.js +2 -0
  55. package/dist/attachment-retirement-scheduler.js.map +1 -0
  56. package/dist/call-option-snapshot.d.ts +20 -0
  57. package/dist/call-option-snapshot.js +2 -0
  58. package/dist/call-option-snapshot.js.map +1 -0
  59. package/dist/compile-call-target-bindings.d.ts +14 -0
  60. package/dist/compile-call-target-bindings.js +2 -0
  61. package/dist/compile-call-target-bindings.js.map +1 -0
  62. package/dist/compile-module-template.d.ts +38 -0
  63. package/dist/compile-module-template.js +2 -0
  64. package/dist/compile-module-template.js.map +1 -0
  65. package/dist/condition-group-execution.d.ts +44 -0
  66. package/dist/condition-group-execution.js +2 -0
  67. package/dist/condition-group-execution.js.map +1 -0
  68. package/dist/condition-override.d.ts +28 -0
  69. package/dist/condition-override.js +2 -0
  70. package/dist/condition-override.js.map +1 -0
  71. package/dist/condition-source.d.ts +10 -0
  72. package/dist/condition-source.js +2 -0
  73. package/dist/condition-source.js.map +1 -0
  74. package/dist/condition-types.d.ts +14 -0
  75. package/dist/condition.d.ts +28 -0
  76. package/dist/condition.js +2 -0
  77. package/dist/condition.js.map +1 -0
  78. package/dist/control-registry.d.ts +56 -0
  79. package/dist/control-registry.js +2 -0
  80. package/dist/control-registry.js.map +1 -0
  81. package/dist/dynamic-scope-child.d.ts +23 -0
  82. package/dist/dynamic-scope-child.js +2 -0
  83. package/dist/dynamic-scope-child.js.map +1 -0
  84. package/dist/dynamic-scope-controller.d.ts +16 -0
  85. package/dist/dynamic-scope-controller.js +2 -0
  86. package/dist/dynamic-scope-controller.js.map +1 -0
  87. package/dist/feature-attachment-authoring-types.d.ts +51 -0
  88. package/dist/feature-attachment-lowering.d.ts +18 -0
  89. package/dist/feature-attachment-lowering.js +2 -0
  90. package/dist/feature-attachment-lowering.js.map +1 -0
  91. package/dist/feature-attachment.d.ts +45 -0
  92. package/dist/feature-attachment.js +2 -0
  93. package/dist/feature-attachment.js.map +1 -0
  94. package/dist/feature-authoring-types.d.ts +196 -0
  95. package/dist/feature-authoring.d.ts +15 -0
  96. package/dist/feature-authoring.js +2 -0
  97. package/dist/feature-authoring.js.map +1 -0
  98. package/dist/feature-body.d.ts +53 -0
  99. package/dist/feature-body.js +2 -0
  100. package/dist/feature-body.js.map +1 -0
  101. package/dist/feature-call-authority.d.ts +8 -0
  102. package/dist/feature-call-authority.js +2 -0
  103. package/dist/feature-call-authority.js.map +1 -0
  104. package/dist/feature-call-types.d.ts +39 -0
  105. package/dist/feature-call.d.ts +18 -0
  106. package/dist/feature-call.js +2 -0
  107. package/dist/feature-call.js.map +1 -0
  108. package/dist/feature-calls.d.ts +6 -0
  109. package/dist/feature-calls.js +2 -0
  110. package/dist/feature-calls.js.map +1 -0
  111. package/dist/feature-contract.d.ts +70 -0
  112. package/dist/feature-contract.js +2 -0
  113. package/dist/feature-contract.js.map +1 -0
  114. package/dist/feature-contribution-model.d.ts +38 -0
  115. package/dist/feature-contribution-model.js +2 -0
  116. package/dist/feature-contribution-model.js.map +1 -0
  117. package/dist/feature-contribution.d.ts +126 -0
  118. package/dist/feature-contribution.js +2 -0
  119. package/dist/feature-contribution.js.map +1 -0
  120. package/dist/feature-definition-api.d.ts +53 -0
  121. package/dist/feature-definition-support.d.ts +21 -0
  122. package/dist/feature-definition-support.js +2 -0
  123. package/dist/feature-definition-support.js.map +1 -0
  124. package/dist/feature-effect.d.ts +26 -0
  125. package/dist/feature-effect.js +2 -0
  126. package/dist/feature-effect.js.map +1 -0
  127. package/dist/feature-event.d.ts +32 -0
  128. package/dist/feature-event.js +2 -0
  129. package/dist/feature-event.js.map +1 -0
  130. package/dist/feature-generation.d.ts +34 -0
  131. package/dist/feature-generation.js +2 -0
  132. package/dist/feature-generation.js.map +1 -0
  133. package/dist/feature-lazy-generation.d.ts +6 -0
  134. package/dist/feature-lazy-generation.js +2 -0
  135. package/dist/feature-lazy-generation.js.map +1 -0
  136. package/dist/feature-lazy.d.ts +44 -0
  137. package/dist/feature-lazy.js +2 -0
  138. package/dist/feature-lazy.js.map +1 -0
  139. package/dist/feature-materialization-binding.d.ts +31 -0
  140. package/dist/feature-materialization-binding.js +2 -0
  141. package/dist/feature-materialization-binding.js.map +1 -0
  142. package/dist/feature-materialization-types.d.ts +45 -0
  143. package/dist/feature-model-dependencies.d.ts +23 -0
  144. package/dist/feature-model-dependencies.js +2 -0
  145. package/dist/feature-model-dependencies.js.map +1 -0
  146. package/dist/feature-model.d.ts +60 -0
  147. package/dist/feature-model.js +2 -0
  148. package/dist/feature-model.js.map +1 -0
  149. package/dist/feature-optional.d.ts +15 -0
  150. package/dist/feature-optional.js +2 -0
  151. package/dist/feature-optional.js.map +1 -0
  152. package/dist/feature-own-lowering.d.ts +22 -0
  153. package/dist/feature-own-lowering.js +2 -0
  154. package/dist/feature-own-lowering.js.map +1 -0
  155. package/dist/feature-port-binding.d.ts +6 -0
  156. package/dist/feature-port-binding.js +2 -0
  157. package/dist/feature-port-binding.js.map +1 -0
  158. package/dist/feature-port.d.ts +66 -0
  159. package/dist/feature-port.js +2 -0
  160. package/dist/feature-port.js.map +1 -0
  161. package/dist/feature-record.d.ts +6 -0
  162. package/dist/feature-record.js +2 -0
  163. package/dist/feature-record.js.map +1 -0
  164. package/dist/feature-resource.d.ts +29 -0
  165. package/dist/feature-resource.js +2 -0
  166. package/dist/feature-resource.js.map +1 -0
  167. package/dist/feature-scope-types.d.ts +35 -0
  168. package/dist/feature-scope.d.ts +29 -0
  169. package/dist/feature-scope.js +2 -0
  170. package/dist/feature-scope.js.map +1 -0
  171. package/dist/feature-stream.d.ts +37 -0
  172. package/dist/feature-stream.js +2 -0
  173. package/dist/feature-stream.js.map +1 -0
  174. package/dist/feature-timers.d.ts +14 -0
  175. package/dist/feature-timers.js +2 -0
  176. package/dist/feature-timers.js.map +1 -0
  177. package/dist/index.d.ts +19 -0
  178. package/dist/index.js +2 -0
  179. package/dist/index.js.map +1 -0
  180. package/dist/inspection-activity-protocol.d.ts +63 -0
  181. package/dist/inspection-activity.d.ts +54 -0
  182. package/dist/inspection-activity.js +2 -0
  183. package/dist/inspection-activity.js.map +1 -0
  184. package/dist/inspection-diff.d.ts +10 -0
  185. package/dist/inspection-diff.js +2 -0
  186. package/dist/inspection-diff.js.map +1 -0
  187. package/dist/inspection-module-activity.d.ts +4 -0
  188. package/dist/inspection-module-activity.js +2 -0
  189. package/dist/inspection-module-activity.js.map +1 -0
  190. package/dist/inspection-observer.d.ts +38 -0
  191. package/dist/inspection-observer.js +2 -0
  192. package/dist/inspection-observer.js.map +1 -0
  193. package/dist/inspection-plan.d.ts +33 -0
  194. package/dist/inspection-plan.js +2 -0
  195. package/dist/inspection-plan.js.map +1 -0
  196. package/dist/inspection-protocol.d.ts +333 -0
  197. package/dist/inspection-protocol.js +2 -0
  198. package/dist/inspection-protocol.js.map +1 -0
  199. package/dist/inspection-registry.d.ts +41 -0
  200. package/dist/inspection-registry.js +2 -0
  201. package/dist/inspection-registry.js.map +1 -0
  202. package/dist/inspection-session.d.ts +92 -0
  203. package/dist/inspection-session.js +2 -0
  204. package/dist/inspection-session.js.map +1 -0
  205. package/dist/inspection-snapshot.d.ts +4 -0
  206. package/dist/inspection-snapshot.js +2 -0
  207. package/dist/inspection-snapshot.js.map +1 -0
  208. package/dist/inspection-state.d.ts +91 -0
  209. package/dist/inspection-state.js +2 -0
  210. package/dist/inspection-state.js.map +1 -0
  211. package/dist/instance-demand.d.ts +31 -0
  212. package/dist/instance-demand.js +2 -0
  213. package/dist/instance-demand.js.map +1 -0
  214. package/dist/internal.d.ts +41 -0
  215. package/dist/internal.js +2 -0
  216. package/dist/internal.js.map +1 -0
  217. package/dist/keyed-scope-controller.d.ts +11 -0
  218. package/dist/keyed-scope-controller.js +2 -0
  219. package/dist/keyed-scope-controller.js.map +1 -0
  220. package/dist/model-kernel.d.ts +18 -0
  221. package/dist/model-kernel.js +2 -0
  222. package/dist/model-kernel.js.map +1 -0
  223. package/dist/module-call-context.d.ts +7 -0
  224. package/dist/module-call-context.js +2 -0
  225. package/dist/module-call-context.js.map +1 -0
  226. package/dist/module-call-runtime.d.ts +21 -0
  227. package/dist/module-call-runtime.js +2 -0
  228. package/dist/module-call-runtime.js.map +1 -0
  229. package/dist/module-generation.d.ts +57 -0
  230. package/dist/module-generation.js +2 -0
  231. package/dist/module-generation.js.map +1 -0
  232. package/dist/module-instance-types.d.ts +156 -0
  233. package/dist/module-instance.d.ts +19 -0
  234. package/dist/module-instance.js +2 -0
  235. package/dist/module-instance.js.map +1 -0
  236. package/dist/module-runtime-identity.d.ts +4 -0
  237. package/dist/module-runtime-identity.js +2 -0
  238. package/dist/module-runtime-identity.js.map +1 -0
  239. package/dist/module-scope-open.d.ts +4 -0
  240. package/dist/module-scope-open.js +2 -0
  241. package/dist/module-scope-open.js.map +1 -0
  242. package/dist/module-scope-retirement.d.ts +3 -0
  243. package/dist/module-scope-retirement.js +2 -0
  244. package/dist/module-scope-retirement.js.map +1 -0
  245. package/dist/module-template-ir.d.ts +59 -0
  246. package/dist/owner-generation-retirement.d.ts +6 -0
  247. package/dist/owner-generation-retirement.js +2 -0
  248. package/dist/owner-generation-retirement.js.map +1 -0
  249. package/dist/owner-generation-state.d.ts +123 -0
  250. package/dist/owner-generation-state.js +2 -0
  251. package/dist/owner-generation-state.js.map +1 -0
  252. package/dist/owner-generation.d.ts +16 -0
  253. package/dist/owner-generation.js +2 -0
  254. package/dist/owner-generation.js.map +1 -0
  255. package/dist/public-module-definition.d.ts +9 -0
  256. package/dist/public-module-definition.js +2 -0
  257. package/dist/public-module-definition.js.map +1 -0
  258. package/dist/public-module-instance.d.ts +6 -0
  259. package/dist/public-module-instance.js +2 -0
  260. package/dist/public-module-instance.js.map +1 -0
  261. package/dist/public-module-retirement-diagnostics.d.ts +5 -0
  262. package/dist/public-module-retirement-diagnostics.js +2 -0
  263. package/dist/public-module-retirement-diagnostics.js.map +1 -0
  264. package/dist/public-module-retirement.d.ts +3 -0
  265. package/dist/public-module-retirement.js +2 -0
  266. package/dist/public-module-retirement.js.map +1 -0
  267. package/dist/public-module-scope.d.ts +5 -0
  268. package/dist/public-module-scope.js +2 -0
  269. package/dist/public-module-scope.js.map +1 -0
  270. package/dist/public-module-state.d.ts +28 -0
  271. package/dist/public-module-state.js +2 -0
  272. package/dist/public-module-state.js.map +1 -0
  273. package/dist/public-module-types.d.ts +295 -0
  274. package/dist/public-module.d.ts +4 -0
  275. package/dist/resource-cache.d.ts +12 -0
  276. package/dist/resource-cache.js +2 -0
  277. package/dist/resource-cache.js.map +1 -0
  278. package/dist/resource-controller.d.ts +29 -0
  279. package/dist/resource-controller.js +2 -0
  280. package/dist/resource-controller.js.map +1 -0
  281. package/dist/resource-policy.d.ts +16 -0
  282. package/dist/resource-policy.js +2 -0
  283. package/dist/resource-policy.js.map +1 -0
  284. package/dist/resource-snapshot.d.ts +12 -0
  285. package/dist/resource-snapshot.js +2 -0
  286. package/dist/resource-snapshot.js.map +1 -0
  287. package/dist/resource-types.d.ts +3 -0
  288. package/dist/runtime-error-reporting.d.ts +4 -0
  289. package/dist/runtime-error-reporting.js +2 -0
  290. package/dist/runtime-error-reporting.js.map +1 -0
  291. package/dist/stream-backpressure.d.ts +18 -0
  292. package/dist/stream-backpressure.js +2 -0
  293. package/dist/stream-backpressure.js.map +1 -0
  294. package/dist/stream-cleanup.d.ts +17 -0
  295. package/dist/stream-cleanup.js +2 -0
  296. package/dist/stream-cleanup.js.map +1 -0
  297. package/dist/stream-controller-types.d.ts +51 -0
  298. package/dist/stream-controller.d.ts +5 -0
  299. package/dist/stream-controller.js +2 -0
  300. package/dist/stream-controller.js.map +1 -0
  301. package/docs/agent-guide.md +214 -0
  302. package/docs/agent-guide.ru.md +208 -0
  303. package/docs/cookbook.md +734 -0
  304. package/docs/cookbook.ru.md +729 -0
  305. package/docs/decisions.md +1437 -0
  306. package/docs/devtools.md +423 -0
  307. package/docs/devtools.ru.md +419 -0
  308. package/docs/how-it-works.md +521 -0
  309. package/docs/how-it-works.ru.md +495 -0
  310. package/docs/releases.md +78 -0
  311. package/docs/releases.ru.md +78 -0
  312. package/docs/spec.md +874 -0
  313. package/docs/spec.ru.md +884 -0
  314. package/package.json +72 -0
@@ -0,0 +1,17 @@
1
+ import type { ResourceRequestKey } from './resource-types.js';
2
+ import type { StreamExecution } from './stream-controller-types.js';
3
+ /** One physical disposer at a time; a failed cleanup keeps its disposer available for an exact retry. */
4
+ declare function disposeStreamExecution<Target, Key extends ResourceRequestKey, Data>(current: StreamExecution<Target, Key, Data>): Promise<void>;
5
+ declare function collectCleanupSettlements(operations: readonly Promise<void>[], failures: Error[]): Promise<void>;
6
+ declare function throwCleanupFailures(failures: readonly Error[]): void;
7
+ /**
8
+ * A retirement handle taken before the work it stands for begins. Aborting a connection runs foreign code that may
9
+ * start a successor, and the successor has to wait for the drain of what it replaces, so the promise exists before
10
+ * the abort and is filled with the real drain right after it (D192).
11
+ */
12
+ declare function reserveCleanupHandle(): {
13
+ readonly promise: Promise<void>;
14
+ readonly settle: (drain: Promise<void>) => void;
15
+ };
16
+ declare function settleCleanupOperations(operations: readonly Promise<void>[]): Promise<void>;
17
+ export { collectCleanupSettlements, disposeStreamExecution, reserveCleanupHandle, settleCleanupOperations, throwCleanupFailures, };
@@ -0,0 +1,2 @@
1
+ import{createErrorWithCause as a}from"@opetope/core/internal";function u(e){if(e.cleanup!==void 0)return e.cleanup;const n=e.disposer;if(n===void 0)return Promise.resolve();let s;try{s=n()}catch(o){return Promise.reject(o)}const t=Promise.resolve(s).then(()=>{e.disposer===n&&(e.disposer=void 0),e.cleanup=void 0});return e.cleanup=t,t.catch(()=>{e.cleanup===t&&(e.cleanup=void 0)}),t}function l(e){return e instanceof Error?e:a("Stream cleanup failed.",e)}async function r(e,n){const s=await Promise.allSettled(e);for(const t of s)t.status==="rejected"&&n.push(l(t.reason))}function i(e){const[n]=e;if(n!==void 0)throw n}function c(){let e;return{promise:new Promise((s,t)=>{e=o=>{o.then(s,t)}}),settle:e}}async function f(e){const n=[];await r(e,n),i(n)}export{r as collectCleanupSettlements,u as disposeStreamExecution,c as reserveCleanupHandle,f as settleCleanupOperations,i as throwCleanupFailures};
2
+ //# sourceMappingURL=stream-cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-cleanup.js","sources":["../src/stream-cleanup.ts"],"sourcesContent":["import { createErrorWithCause } from '@opetope/core/internal';\nimport type { Awaitable } from '@opetope/core/internal';\n\nimport type { ResourceRequestKey } from './resource-types';\nimport type { StreamExecution } from './stream-controller-types';\n\n/** One physical disposer at a time; a failed cleanup keeps its disposer available for an exact retry. */\nfunction disposeStreamExecution<Target, Key extends ResourceRequestKey, Data>(\n current: StreamExecution<Target, Key, Data>,\n): Promise<void> {\n if (current.cleanup !== undefined) return current.cleanup;\n\n const disposer = current.disposer;\n\n if (disposer === undefined) return Promise.resolve();\n\n let result: Awaitable<void>;\n\n try {\n result = disposer();\n } catch (error) {\n return Promise.reject(error);\n }\n\n const cleanup = Promise.resolve(result).then(() => {\n if (current.disposer === disposer) current.disposer = undefined;\n\n current.cleanup = undefined;\n });\n current.cleanup = cleanup;\n void cleanup.catch(() => {\n if (current.cleanup === cleanup) current.cleanup = undefined;\n });\n\n return cleanup;\n}\n\nfunction cleanupError(value: unknown): Error {\n return value instanceof Error ? value : createErrorWithCause('Stream cleanup failed.', value);\n}\n\nasync function collectCleanupSettlements(operations: readonly Promise<void>[], failures: Error[]): Promise<void> {\n const results = await Promise.allSettled(operations);\n\n for (const result of results) {\n if (result.status === 'rejected') failures.push(cleanupError(result.reason));\n }\n}\n\nfunction throwCleanupFailures(failures: readonly Error[]): void {\n const [first] = failures;\n\n if (first === undefined) return;\n\n throw first;\n}\n\n/**\n * A retirement handle taken before the work it stands for begins. Aborting a connection runs foreign code that may\n * start a successor, and the successor has to wait for the drain of what it replaces, so the promise exists before\n * the abort and is filled with the real drain right after it (D192).\n */\nfunction reserveCleanupHandle(): {\n readonly promise: Promise<void>;\n readonly settle: (drain: Promise<void>) => void;\n} {\n let settle!: (drain: Promise<void>) => void;\n const promise = new Promise<void>((resolve, reject) => {\n settle = (drain: Promise<void>): void => void drain.then(resolve, reject);\n });\n\n return { promise, settle };\n}\n\nasync function settleCleanupOperations(operations: readonly Promise<void>[]): Promise<void> {\n const failures: Error[] = [];\n await collectCleanupSettlements(operations, failures);\n throwCleanupFailures(failures);\n}\n\nexport {\n collectCleanupSettlements,\n disposeStreamExecution,\n reserveCleanupHandle,\n settleCleanupOperations,\n throwCleanupFailures,\n};\n"],"names":["disposeStreamExecution","current","disposer","result","error","cleanup","cleanupError","value","createErrorWithCause","collectCleanupSettlements","operations","failures","results","throwCleanupFailures","first","reserveCleanupHandle","settle","resolve","reject","drain","settleCleanupOperations"],"mappings":"8DAOA,SAASA,EACPC,EAA2C,CAE3C,GAAIA,EAAQ,UAAY,OAAW,OAAOA,EAAQ,QAElD,MAAMC,EAAWD,EAAQ,SAEzB,GAAIC,IAAa,OAAW,OAAO,QAAQ,QAAO,EAElD,IAAIC,EAEJ,GAAI,CACFA,EAASD,EAAQ,CACnB,OAASE,EAAO,CACd,OAAO,QAAQ,OAAOA,CAAK,CAC7B,CAEA,MAAMC,EAAU,QAAQ,QAAQF,CAAM,EAAE,KAAK,IAAK,CAC5CF,EAAQ,WAAaC,IAAUD,EAAQ,SAAW,QAEtDA,EAAQ,QAAU,MACpB,CAAC,EACD,OAAAA,EAAQ,QAAUI,EACbA,EAAQ,MAAM,IAAK,CAClBJ,EAAQ,UAAYI,IAASJ,EAAQ,QAAU,OACrD,CAAC,EAEMI,CACT,CAEA,SAASC,EAAaC,EAAc,CAClC,OAAOA,aAAiB,MAAQA,EAAQC,EAAqB,yBAA0BD,CAAK,CAC9F,CAEA,eAAeE,EAA0BC,EAAsCC,EAAiB,CAC9F,MAAMC,EAAU,MAAM,QAAQ,WAAWF,CAAU,EAEnD,UAAWP,KAAUS,EACfT,EAAO,SAAW,YAAYQ,EAAS,KAAKL,EAAaH,EAAO,MAAM,CAAC,CAE/E,CAEA,SAASU,EAAqBF,EAA0B,CACtD,KAAM,CAACG,CAAK,EAAIH,EAEhB,GAAIG,IAAU,OAEd,MAAMA,CACR,CAOA,SAASC,GAAoB,CAI3B,IAAIC,EAKJ,MAAO,CAAE,QAJO,IAAI,QAAc,CAACC,EAASC,IAAU,CACpDF,EAAUG,GAAoB,CAAgBA,EAAM,KAAKF,EAASC,CAAM,EAC1E,CAAC,EAEiB,OAAAF,CAAM,CAC1B,CAEA,eAAeI,EAAwBV,EAAoC,CACzE,MAAMC,EAAoB,CAAA,EAC1B,MAAMF,EAA0BC,EAAYC,CAAQ,EACpDE,EAAqBF,CAAQ,CAC/B"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * What one stream controller is made of: the options an author gives it, the two contexts it hands back and the
3
+ * record of one live execution. Kept apart from the machine so the machine stays under one screen of law per file.
4
+ */
5
+ import type { Readable } from '@opetope/core';
6
+ import type { Awaitable } from '@opetope/core/internal';
7
+ import type { Resource, ResourceRequestKey, ResourceRetention, ResourceRetry, StreamBackpressure } from './resource-types.js';
8
+ import type { RuntimeErrorReporter } from './runtime-error-reporting.js';
9
+ import type { StreamAdmission } from './stream-backpressure.js';
10
+ type StreamDisposer = () => Awaitable<void>;
11
+ type StreamConnectContext<Source, Data> = {
12
+ readonly emit: (data: Data) => void;
13
+ readonly signal: AbortSignal;
14
+ readonly source: Source;
15
+ };
16
+ type StreamConsumeContext<Source, Target, Data> = {
17
+ readonly data: Data;
18
+ readonly signal: AbortSignal;
19
+ readonly source: Source;
20
+ readonly target: Target;
21
+ };
22
+ type StreamControllerOptions<Source, Target, Data, Key extends ResourceRequestKey> = {
23
+ readonly allocateGeneration?: () => number;
24
+ readonly backpressure: StreamBackpressure;
25
+ readonly connect: (target: Target, context: StreamConnectContext<Source, Data>) => Awaitable<StreamDisposer | void>;
26
+ readonly consume?: (context: StreamConsumeContext<Source, Target, Data>) => Awaitable<void>;
27
+ readonly key: (target: Target) => Key;
28
+ readonly reporter?: RuntimeErrorReporter;
29
+ readonly retention: ResourceRetention;
30
+ readonly retry?: ResourceRetry;
31
+ readonly source: Source;
32
+ /** The public selector may answer `T | null | undefined`; both nullish answers mean «no target» (D194). */
33
+ readonly target: Readable<Target | null | undefined>;
34
+ };
35
+ interface ManagedStreamRef<Data, Key extends ResourceRequestKey> extends Resource<Data, Key> {
36
+ readonly close: () => Promise<void>;
37
+ readonly fence: () => void;
38
+ }
39
+ type StreamExecution<Target, Key extends ResourceRequestKey, Data> = {
40
+ readonly abort: AbortController;
41
+ admission: StreamAdmission<Data> | undefined;
42
+ readonly attempt: number;
43
+ cleanup: Promise<void> | undefined;
44
+ disposer: StreamDisposer | undefined;
45
+ readonly generation: number;
46
+ readonly key: Key;
47
+ retirement: Promise<void> | undefined;
48
+ settlement: Promise<void> | undefined;
49
+ readonly target: Target;
50
+ };
51
+ export type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer, StreamExecution, };
@@ -0,0 +1,5 @@
1
+ import type { ResourceRequestKey } from './resource-types.js';
2
+ import type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer } from './stream-controller-types.js';
3
+ declare function createStreamController<Source, Target, Data, Key extends ResourceRequestKey>(options: StreamControllerOptions<Source, Target, Data, Key>, register?: (controller: ManagedStreamRef<Data, Key>) => void): ManagedStreamRef<Data, Key>;
4
+ export { createStreamController };
5
+ export type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer };
@@ -0,0 +1,2 @@
1
+ import{abortWithReason as me}from"@opetope/core/internal";import{createRetainedResourceCache as ye}from"./resource-cache.js";import{idleSnapshot as M,pendingSnapshot as ee,readResourceData as te}from"./resource-snapshot.js";import{reportRuntimeFailure as pe}from"./runtime-error-reporting.js";import{createStreamAdmission as ge}from"./stream-backpressure.js";import{collectCleanupSettlements as ne,throwCleanupFailures as he,reserveCleanupHandle as ve,disposeStreamExecution as Se,settleCleanupOperations as B}from"./stream-cleanup.js";function we(i,b){var $,A;const q=new Set;let u;const c=ye(i.retention),re=(($=i.retry)==null?void 0:$.attempts)??1,ie=((A=i.retry)==null?void 0:A.delayMs)??0,k=new Set,R=new Set;let s=!1,E,y=!1,S=!1,z=0,T=0,o,g=0,P=0,j,d,l,w=0,h=M,O;const D=()=>{var e;return w+=1,g=((e=i.allocateGeneration)==null?void 0:e.call(i))??g+1,g},f=e=>pe(e,i.reporter),H=e=>s&&!y&&!S&&e===g,x=e=>o===e&&!e.abort.signal.aborted&&s&&e.generation===g,J=e=>s&&!y&&!S&&w===e,se=()=>{var e;(e=u==null?void 0:u.changed)==null||e.call(u);for(const t of[...q])try{t()}catch(n){f(n)}},v=e=>{h=Object.freeze(e),se()},L=()=>{j!==void 0&&(clearTimeout(j),j=void 0)},N=e=>{const t=e.admission;if(e.admission=void 0,t===void 0)return Promise.resolve();const n=t.close();return F(n),n},F=e=>{k.add(e),e.then(()=>k.delete(e),()=>k.delete(e))},I=(e,t,n)=>{if(e.retirement!==void 0)return e.retirement;const r=ve();e.retirement=r.promise,R.add(e);try{n==null||n(),me(e.abort,t)}finally{const p=Promise.resolve(e.settlement).then(()=>Se(e));r.settle(B([p,N(e)]))}return r.promise.then(()=>{e.retirement===r.promise&&(e.retirement=void 0),R.delete(e)},()=>{e.retirement===r.promise&&(e.retirement=void 0)}),r.promise},Q=e=>I(e),G=e=>{L();const t=o;if(o=void 0,t===void 0)return e==null||e(),Promise.resolve();const n=I(t,void 0,e);return F(n),n},U=(e,t,n)=>{if(x(e)){if(o=void 0,F(I(e,t)),H(e.generation)&&o===void 0){const r=te(h);v({attempt:e.attempt,data:r.present?r.data:void 0,error:t,generation:e.generation,key:e.key,retryable:!0,stale:r.present,status:"error"})}f(t)}},ae=(e,t)=>{var n;if(x(e)&&(c==null||c.set(e.key,t),v({attempt:e.attempt,data:t,error:void 0,generation:e.generation,key:e.key,stale:!1,status:"ready"}),!!x(e)))return(n=i.consume)==null?void 0:n.call(i,Object.freeze({data:t,signal:e.abort.signal,source:i.source,target:e.target}))},V=e=>{if(!x(e))return;const t=ge({consume:a=>ae(e,a),onConsumeError:f});e.admission=t;let n=()=>{},r=()=>{};const p=new Promise((a,m)=>{n=m,r=a});e.settlement=p,F(p);let C;try{C=i.connect(e.target,Object.freeze({emit:t.emit,signal:e.abort.signal,source:i.source}))}catch(a){C=Promise.reject(a)}Promise.resolve(C).then(a=>{try{if(a!==void 0&&typeof a!="function"){U(e,new TypeError("Stream connect must return void or a disposer function."),!0),r();return}e.disposer=a??void 0,r()}catch(m){n(m)}},a=>{try{if(!x(e)){r();return}if(e.attempt<re){const m=N(e);j=setTimeout(()=>{j=void 0,m.then(()=>{K(e.attempt+1,e.generation,e.key,e.target)})},ie),r();return}U(e,a,!0),r()}catch(m){n(m)}})},K=(e,t,n,r)=>{if(!H(t))return;const p=o!==void 0||R.size!==0,C=[...R].map(Q),a=G();if(!H(t)){B([a,...C]).catch(f);return}const m={abort:new AbortController,admission:void 0,attempt:e,cleanup:void 0,disposer:void 0,generation:t,key:n,retirement:void 0,settlement:void 0,target:r};if(o=m,v(ee(e,t,n,te(h))),!p){V(m);return}B([a,...C]).catch(f).then(()=>V(m))},W=(e,t)=>{const n=c==null?void 0:c.get(t),r=n===void 0?{present:!1}:{data:n.data,present:!0},p=D();v(ee(1,p,t,r)),K(1,p,t,e)},oe=()=>{if(d===void 0&&l===void 0&&h.status==="idle")return;d=void 0,l=void 0;const e=D();G().catch(f),g===e&&v({...M,generation:e})},de=(e,t)=>{let n;try{n=i.key(e)}catch(r){f(r);return}if(J(t)){if(d!==void 0&&Object.is(d,n)){l=e;return}d=n,l=e,W(e,n)}},X=()=>{if(!s||y)return;const e=++w;let t;try{t=i.target.getSnapshot()}catch(n){f(n);return}if(J(e)){if(t==null){oe();return}de(t,e)}},ce=e=>{try{e()}catch(t){f(t)}},le=e=>!y&&!S&&s&&z===e,Y=()=>{if(s||y||S)return;s=!0;const e=z+=1;try{const t=i.target.subscribe(X);if(!le(e)){ce(t);return}O=t,X()}catch(t){s=!1,O=void 0,f(t)}},Z=()=>{const e=O;O=void 0;try{e==null||e()}catch(t){f(t)}},fe=()=>{if(!s||i.retention.kind==="scope"||P!==0)return;w+=1,s=!1,z+=1,T+=1,d=void 0,l=void 0;const e=D();G(Z).catch(f),!(s||y||g!==e)&&v({...M,generation:e})},ue=()=>{const e=++T;queueMicrotask(()=>{e===T&&fe()})},_=Object.freeze({close:()=>{if(E!==void 0)return E;w+=1;const e=[...R].map(Q);let t=Promise.resolve();if(!y){y=!0,s=!1,z+=1,T+=1,P=0,t=G(),Z(),d=void 0,l=void 0,c==null||c.clear();const r=D();v({...M,generation:r})}const n=(async()=>{const r=[];for(await ne([t,...e],r);k.size!==0;)await ne([...k],r);he(r)})();return E=n,n.catch(()=>{E===n&&(E=void 0)}),n},fence(){var e,t;w+=1,S=!0,s=!1,L(),o==null||o.abort.abort(),(e=o==null?void 0:o.admission)==null||e.close(),(t=u==null?void 0:u.changed)==null||t.call(u)},getSnapshot:()=>h,inspectionState:()=>S||y?"fenced":h.status,invalidate:()=>{c==null||c.clear(),!(!s||d===void 0||l===void 0)&&W(l,d)},observe:e=>{u=e},refresh:()=>{!s||d===void 0||l===void 0||W(l,d)},retain:()=>{if(y)return()=>{};P+=1,T+=1,Y();let e=!0;return()=>{e&&(e=!1,P-=1,P===0&&i.retention.kind==="observer"&&ue())}},retry:()=>{!s||h.status!=="error"||d===void 0||l===void 0||K(h.attempt+1,g,d,l)},subscribe(e){if(typeof e!="function")throw new TypeError("Stream listener must be a function.");q.add(e);let t=!0;return()=>{t&&(t=!1,q.delete(e))}}});return b==null||b(_),i.retention.kind==="scope"&&Y(),_}export{we as createStreamController};
2
+ //# sourceMappingURL=stream-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-controller.js","sources":["../src/stream-controller.ts"],"sourcesContent":["import type { Awaitable, CallObservationSlot } from '@opetope/core/internal';\nimport { abortWithReason } from '@opetope/core/internal';\n\nimport { createRetainedResourceCache } from './resource-cache';\nimport type { ResourceCache } from './resource-cache';\nimport { idleSnapshot, pendingSnapshot, readResourceData } from './resource-snapshot';\nimport type { ResourceData } from './resource-snapshot';\nimport type { ResourceRequestKey, ResourceSnapshot } from './resource-types';\nimport { reportRuntimeFailure } from './runtime-error-reporting';\nimport { createStreamAdmission } from './stream-backpressure';\nimport {\n collectCleanupSettlements,\n disposeStreamExecution,\n reserveCleanupHandle,\n settleCleanupOperations,\n throwCleanupFailures,\n} from './stream-cleanup';\nimport type {\n ManagedStreamRef,\n StreamConnectContext,\n StreamConsumeContext,\n StreamControllerOptions,\n StreamDisposer,\n StreamExecution,\n} from './stream-controller-types';\n\nfunction createStreamController<Source, Target, Data, Key extends ResourceRequestKey>(\n options: StreamControllerOptions<Source, Target, Data, Key>,\n register?: (controller: ManagedStreamRef<Data, Key>) => void,\n): ManagedStreamRef<Data, Key> {\n const listeners = new Set<() => void>();\n let observation: CallObservationSlot | undefined;\n const cache: ResourceCache<Data, Key> | undefined = createRetainedResourceCache(options.retention);\n const maximumAttempts = options.retry?.attempts ?? 1;\n const retryDelayMs = options.retry?.delayMs ?? 0;\n const draining = new Set<Promise<void>>();\n const retiredExecutions = new Set<StreamExecution<Target, Key, Data>>();\n let active = false;\n let closePromise: Promise<void> | undefined;\n let closed = false;\n let fenced = false;\n /** Which activation owns the target subscription; a retain that only cancels a grace period never changes it. */\n let activation = 0;\n /** The pending deactivation of an observer retention: any retain cancels it without touching the activation. */\n let graceRevision = 0;\n let execution: StreamExecution<Target, Key, Data> | undefined;\n let generation = 0;\n let retainers = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let selectedKey: Key | undefined;\n let selectedTarget: Target | undefined;\n let selectionRevision = 0;\n let snapshot = idleSnapshot as ResourceSnapshot<Data, Key>;\n let unsubscribeTarget: (() => void) | undefined;\n\n const allocateGeneration = (): number => {\n selectionRevision += 1;\n generation = options.allocateGeneration?.() ?? generation + 1;\n\n return generation;\n };\n const report = (error: unknown): void => reportRuntimeFailure(error, options.reporter);\n const admitsExecution = (expectedGeneration: number): boolean =>\n active && !closed && !fenced && expectedGeneration === generation;\n const isCurrentExecution = (current: StreamExecution<Target, Key, Data>): boolean =>\n execution === current && !current.abort.signal.aborted && active && current.generation === generation;\n const isCurrentRead = (revision: number): boolean => active && !closed && !fenced && selectionRevision === revision;\n const notify = (): void => {\n observation?.changed?.();\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch (error) {\n report(error);\n }\n }\n };\n const publish = (next: ResourceSnapshot<Data, Key>): void => {\n snapshot = Object.freeze(next);\n notify();\n };\n const clearRetry = (): void => {\n if (retryTimer === undefined) return;\n\n clearTimeout(retryTimer);\n retryTimer = undefined;\n };\n /**\n * Closing admission returns the drain of work it already admitted, and that promise is never dropped: `close()`\n * waits for it, so an imported source is released only after the last `consume` has finished (D192).\n */\n const closeAdmission = (current: StreamExecution<Target, Key, Data>): Promise<void> => {\n const admission = current.admission;\n current.admission = undefined;\n\n if (admission === undefined) return Promise.resolve();\n\n const consumed = admission.close();\n // eslint-disable-next-line no-use-before-define -- the drain set is initialized above this closure.\n trackDrain(consumed);\n\n return consumed;\n };\n const trackDrain = (operation: Promise<void>): void => {\n draining.add(operation);\n void operation.then(\n () => draining.delete(operation),\n () => draining.delete(operation),\n );\n };\n const retireExecution = (\n current: StreamExecution<Target, Key, Data>,\n reason?: unknown,\n beforeAbort?: () => void,\n ): Promise<void> => {\n if (current.retirement !== undefined) return current.retirement;\n\n // D192: the abort may select another target, and that successor must find this execution already retiring.\n const reserved = reserveCleanupHandle();\n current.retirement = reserved.promise;\n retiredExecutions.add(current);\n\n try {\n beforeAbort?.();\n abortWithReason(current.abort, reason);\n } finally {\n // Disconnect may unblock consume; join both before releasing the imported source or opening a successor.\n const disposal = Promise.resolve(current.settlement).then(() => disposeStreamExecution(current));\n reserved.settle(settleCleanupOperations([disposal, closeAdmission(current)]));\n }\n\n void reserved.promise.then(\n () => {\n if (current.retirement === reserved.promise) current.retirement = undefined;\n\n retiredExecutions.delete(current);\n },\n () => {\n if (current.retirement === reserved.promise) current.retirement = undefined;\n },\n );\n\n return reserved.promise;\n };\n const retryRetirement = (current: StreamExecution<Target, Key, Data>): Promise<void> => retireExecution(current);\n const fenceExecution = (beforeAbort?: () => void): Promise<void> => {\n clearRetry();\n const current = execution;\n execution = undefined;\n\n if (current === undefined) {\n beforeAbort?.();\n\n return Promise.resolve();\n }\n\n const disposal = retireExecution(current, undefined, beforeAbort);\n trackDrain(disposal);\n\n return disposal;\n };\n const workspaceFailure = (\n current: StreamExecution<Target, Key, Data>,\n error: unknown,\n reportFailure: boolean,\n ): void => {\n if (!isCurrentExecution(current)) return;\n\n execution = undefined;\n // D192: workspace failure retires the same physical connection as replacement, including its pending consume.\n trackDrain(retireExecution(current, error));\n\n // Abort can select a successor or close the owner; neither gives this failure its snapshot back.\n if (admitsExecution(current.generation) && execution === undefined) {\n const data = readResourceData(snapshot);\n publish({\n attempt: current.attempt,\n data: data.present ? data.data : undefined,\n error,\n generation: current.generation,\n key: current.key,\n retryable: true,\n stale: data.present,\n status: 'error',\n });\n }\n\n if (reportFailure) report(error);\n };\n const consume = (current: StreamExecution<Target, Key, Data>, data: Data): Awaitable<void> => {\n if (!isCurrentExecution(current)) return;\n\n cache?.set(current.key, data);\n publish({\n attempt: current.attempt,\n data,\n error: undefined,\n generation: current.generation,\n key: current.key,\n stale: false,\n status: 'ready',\n });\n\n // D194: publishing `ready` runs foreign subscribers, and one of them may fence, close or retarget the stream.\n // The consumer of a stream nobody waits for any more never starts; work already inside it still drains.\n if (!isCurrentExecution(current)) return;\n\n return options.consume?.(\n Object.freeze({ data, signal: current.abort.signal, source: options.source, target: current.target }),\n );\n };\n const startConnect = (current: StreamExecution<Target, Key, Data>): void => {\n if (!isCurrentExecution(current)) return;\n\n const admission = createStreamAdmission<Data>({\n consume: data => consume(current, data),\n onConsumeError: report,\n });\n current.admission = admission;\n let rejectSettlement: (error: unknown) => void = () => undefined;\n let resolveSettlement: () => void = () => undefined;\n const settlement = new Promise<void>((resolve, reject) => {\n rejectSettlement = reject;\n resolveSettlement = resolve;\n });\n current.settlement = settlement;\n trackDrain(settlement);\n let connection: Awaitable<StreamDisposer | void>;\n\n try {\n connection = options.connect(\n current.target,\n Object.freeze({ emit: admission.emit, signal: current.abort.signal, source: options.source }),\n );\n } catch (error) {\n connection = Promise.reject(error);\n }\n\n void Promise.resolve(connection).then(\n disposer => {\n try {\n if (disposer !== undefined && typeof disposer !== 'function') {\n workspaceFailure(current, new TypeError('Stream connect must return void or a disposer function.'), true);\n resolveSettlement();\n\n return;\n }\n\n current.disposer = disposer ?? undefined;\n resolveSettlement();\n } catch (error) {\n rejectSettlement(error);\n }\n },\n (error: unknown) => {\n try {\n if (!isCurrentExecution(current)) {\n resolveSettlement();\n\n return;\n }\n\n if (current.attempt < maximumAttempts) {\n const consumed = closeAdmission(current);\n retryTimer = setTimeout(() => {\n retryTimer = undefined;\n // A successor never overlaps the consume of its predecessor: the admitted work drains first (D192).\n void consumed.then(() => {\n // eslint-disable-next-line no-use-before-define -- the timer fires after controller construction completes.\n execute(current.attempt + 1, current.generation, current.key, current.target);\n });\n }, retryDelayMs);\n resolveSettlement();\n\n return;\n }\n\n workspaceFailure(current, error, true);\n resolveSettlement();\n } catch (settlementError) {\n rejectSettlement(settlementError);\n }\n },\n );\n };\n const execute = (attempt: number, expectedGeneration: number, key: Key, target: Target): void => {\n if (!admitsExecution(expectedGeneration)) return;\n\n const hasPredecessor = execution !== undefined || retiredExecutions.size !== 0;\n const retired = [...retiredExecutions].map(retryRetirement);\n const previous = fenceExecution();\n\n // D194: retiring the predecessor aborts a foreign connection, and that cleanup may select another target. The\n // newer selection owns the controller from that moment, so this attempt drains what it fenced and walks away.\n if (!admitsExecution(expectedGeneration)) {\n void settleCleanupOperations([previous, ...retired]).catch(report);\n\n return;\n }\n\n const current: StreamExecution<Target, Key, Data> = {\n abort: new AbortController(),\n admission: undefined,\n attempt,\n cleanup: undefined,\n disposer: undefined,\n generation: expectedGeneration,\n key,\n retirement: undefined,\n settlement: undefined,\n target,\n };\n execution = current;\n publish(pendingSnapshot(attempt, expectedGeneration, key, readResourceData(snapshot)));\n\n if (!hasPredecessor) {\n startConnect(current);\n\n return;\n }\n\n void settleCleanupOperations([previous, ...retired])\n .catch(report)\n .then(() => startConnect(current));\n };\n const openSelection = (target: Target, key: Key): void => {\n const cached = cache?.get(key);\n const data: ResourceData<Data> = cached === undefined ? { present: false } : { data: cached.data, present: true };\n const nextGeneration = allocateGeneration();\n publish(pendingSnapshot(1, nextGeneration, key, data));\n execute(1, nextGeneration, key, target);\n };\n const closeSelection = (): void => {\n if (selectedKey === undefined && selectedTarget === undefined && snapshot.status === 'idle') return;\n\n selectedKey = undefined;\n selectedTarget = undefined;\n // D194: the fence aborts a connection whose cleanup may select a target of its own, so this transition\n // reserves its generation first and gives the controller up to whatever the cleanup selected.\n const nextGeneration = allocateGeneration();\n void fenceExecution().catch(report);\n\n if (generation !== nextGeneration) return;\n\n publish({ ...idleSnapshot, generation: nextGeneration });\n };\n const selectTarget = (target: Target, revision: number): void => {\n let key: Key;\n\n try {\n key = options.key(target);\n } catch (error) {\n report(error);\n\n return;\n }\n\n if (!isCurrentRead(revision)) return;\n\n if (selectedKey !== undefined && Object.is(selectedKey, key)) {\n selectedTarget = target;\n\n return;\n }\n\n selectedKey = key;\n selectedTarget = target;\n openSelection(target, key);\n };\n const readTarget = (): void => {\n if (!active || closed) return;\n\n const revision = ++selectionRevision;\n let target: Target | null | undefined;\n\n try {\n target = options.target.getSnapshot();\n } catch (error) {\n report(error);\n\n return;\n }\n\n if (!isCurrentRead(revision)) return;\n\n if (target === null || target === undefined) {\n closeSelection();\n\n return;\n }\n\n selectTarget(target, revision);\n };\n /** The subscription of a stale activation is foreign code too: it is released once and its failure reported. */\n const releaseStaleSubscription = (release: () => void): void => {\n try {\n release();\n } catch (error) {\n report(error);\n }\n };\n /** Whether the activation that took this subscription is still the one the controller waits for (D195). */\n const activationIsCurrent = (current: number): boolean => !closed && !fenced && active && activation === current;\n const activate = (): void => {\n if (active || closed || fenced) return;\n\n active = true;\n const current = (activation += 1);\n\n try {\n // D195: `subscribe` runs foreign code that may close or fence this controller before it returns. The disposer\n // of an activation nobody waits for any more is released here, exactly once, and never recorded.\n const release = options.target.subscribe(readTarget);\n\n if (!activationIsCurrent(current)) {\n releaseStaleSubscription(release);\n\n return;\n }\n\n unsubscribeTarget = release;\n readTarget();\n } catch (error) {\n active = false;\n unsubscribeTarget = undefined;\n report(error);\n }\n };\n const releaseTarget = (): void => {\n const unsubscribe = unsubscribeTarget;\n unsubscribeTarget = undefined;\n\n try {\n unsubscribe?.();\n } catch (error) {\n report(error);\n }\n };\n const deactivate = (): void => {\n if (!active || options.retention.kind === 'scope' || retainers !== 0) return;\n\n selectionRevision += 1;\n active = false;\n activation += 1;\n graceRevision += 1;\n // D195: both the subscription disposer and abort may retain again. Reserve selection and retirement first,\n // then release the old subscription before abort can install a new subscription with the same listener.\n selectedKey = undefined;\n selectedTarget = undefined;\n const nextGeneration = allocateGeneration();\n void fenceExecution(releaseTarget).catch(report);\n\n if (active || closed || generation !== nextGeneration) return;\n\n publish({ ...idleSnapshot, generation: nextGeneration });\n };\n const requestDeactivation = (): void => {\n const revision = ++graceRevision;\n\n queueMicrotask(() => {\n if (revision === graceRevision) deactivate();\n });\n };\n const refresh = (): void => {\n if (!active || selectedKey === undefined || selectedTarget === undefined) return;\n\n openSelection(selectedTarget, selectedKey);\n };\n const retry = (): void => {\n if (!active || snapshot.status !== 'error' || selectedKey === undefined || selectedTarget === undefined) {\n return;\n }\n\n execute(snapshot.attempt + 1, generation, selectedKey, selectedTarget);\n };\n const invalidate = (): void => {\n cache?.clear();\n\n if (!active || selectedKey === undefined || selectedTarget === undefined) return;\n\n openSelection(selectedTarget, selectedKey);\n };\n const retain = (): (() => void) => {\n if (closed) return () => undefined;\n\n retainers += 1;\n // A second lease taken while the first one activates cancels the grace period and keeps that activation alive.\n graceRevision += 1;\n activate();\n let retained = true;\n\n return (): void => {\n if (!retained) return;\n\n retained = false;\n retainers -= 1;\n\n if (retainers === 0 && options.retention.kind === 'observer') requestDeactivation();\n };\n };\n const close = (): Promise<void> => {\n if (closePromise !== undefined) return closePromise;\n\n selectionRevision += 1;\n const retired = [...retiredExecutions].map(retryRetirement);\n let initialRetirement = Promise.resolve();\n\n if (!closed) {\n closed = true;\n active = false;\n activation += 1;\n graceRevision += 1;\n retainers = 0;\n initialRetirement = fenceExecution();\n releaseTarget();\n selectedKey = undefined;\n selectedTarget = undefined;\n cache?.clear();\n const nextGeneration = allocateGeneration();\n publish({ ...idleSnapshot, generation: nextGeneration });\n }\n\n const operation = (async () => {\n const failures: Error[] = [];\n await collectCleanupSettlements([initialRetirement, ...retired], failures);\n\n while (draining.size !== 0) {\n await collectCleanupSettlements([...draining], failures);\n }\n\n throwCleanupFailures(failures);\n })();\n closePromise = operation;\n void operation.catch(() => {\n if (closePromise === operation) closePromise = undefined;\n });\n\n return operation;\n };\n\n const ref: ManagedStreamRef<Data, Key> = Object.freeze({\n close,\n fence(): void {\n selectionRevision += 1;\n fenced = true;\n active = false;\n clearRetry();\n execution?.abort.abort();\n // Preserve the admission until retireExecution captures its physical drain.\n void execution?.admission?.close();\n observation?.changed?.();\n },\n getSnapshot: () => snapshot,\n inspectionState: (): ResourceSnapshot<Data, Key>['status'] | 'fenced' =>\n fenced || closed ? 'fenced' : snapshot.status,\n invalidate,\n observe: (slot: CallObservationSlot): void => {\n observation = slot;\n },\n refresh,\n retain,\n retry,\n subscribe(listener: () => void): () => void {\n if (typeof listener !== 'function') throw new TypeError('Stream listener must be a function.');\n\n listeners.add(listener);\n let subscribed = true;\n\n return (): void => {\n if (!subscribed) return;\n\n subscribed = false;\n listeners.delete(listener);\n };\n },\n });\n\n register?.(ref);\n\n if (options.retention.kind === 'scope') activate();\n\n return ref;\n}\n\nexport { createStreamController };\nexport type { ManagedStreamRef, StreamConnectContext, StreamConsumeContext, StreamControllerOptions, StreamDisposer };\n"],"names":["createStreamController","options","register","listeners","observation","cache","createRetainedResourceCache","maximumAttempts","_a","retryDelayMs","_b","draining","retiredExecutions","active","closePromise","closed","fenced","activation","graceRevision","execution","generation","retainers","retryTimer","selectedKey","selectedTarget","selectionRevision","snapshot","idleSnapshot","unsubscribeTarget","allocateGeneration","report","error","reportRuntimeFailure","admitsExecution","expectedGeneration","isCurrentExecution","current","isCurrentRead","revision","notify","listener","publish","next","clearRetry","closeAdmission","admission","consumed","trackDrain","operation","retireExecution","reason","beforeAbort","reserved","reserveCleanupHandle","abortWithReason","disposal","disposeStreamExecution","settleCleanupOperations","retryRetirement","fenceExecution","workspaceFailure","reportFailure","data","readResourceData","consume","startConnect","createStreamAdmission","rejectSettlement","resolveSettlement","settlement","resolve","reject","connection","disposer","execute","settlementError","attempt","key","target","hasPredecessor","retired","previous","pendingSnapshot","openSelection","cached","nextGeneration","closeSelection","selectTarget","readTarget","releaseStaleSubscription","release","activationIsCurrent","activate","releaseTarget","unsubscribe","deactivate","requestDeactivation","ref","initialRetirement","failures","collectCleanupSettlements","throwCleanupFailures","slot","retained","subscribed"],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ # Opetope: guide for agents and humans writing code on the framework
2
+
3
+ A short, checkable document: how to decide the shape of your code, what is forbidden and which error you will see,
4
+ which checks to run. Details are in [cookbook.md](cookbook.md), the machinery in
5
+ [how-it-works.md](how-it-works.md), the laws in [spec.md](spec.md).
6
+
7
+ ## 1. Where things live
8
+
9
+ Keep feature composition, model implementations and UI in separate layers. The paths below illustrate a
10
+ `features/<f>/{integration,models,ui}` layout; the host owns the application declaration and the bindings supplied
11
+ when it opens that application. The dependency boundaries apply regardless of the directory names.
12
+
13
+ | What you write | Where | Imports |
14
+ | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
15
+ | contracts (`defineHostContract`, `definePort`, `defineSlot`, `defineModel`, `defineCondition`) | `features/<f>/integration/platform/*.contract.ts`, `ui/**/contracts.ts`, `slots.ts`; the consumer owns the target contract, the feature owns the contribution value | `@opetope/core`, `@opetope/runtime`, types; `@opetope/react` for UI slot targets |
16
+ | the feature (`defineFeature`) | `features/<f>/integration/platform/feature.ts(x)` | contracts, its own models/UI, `@opetope/runtime` |
17
+ | models (`defineModel` + factories) | `features/<f>/models/**` | `@opetope/core` |
18
+ | contribution UI (`requiresModels`, hooks) | `features/<f>/ui/**` | `@opetope/core`, `@opetope/react` |
19
+ | application composition and host integration | the application's bootstrap and integration layers | `@opetope/runtime`, `@opetope/react/integration`; internal entries only for low-level integration |
20
+
21
+ Keep the `internal` entries and `@opetope/react/integration` in runtime implementation, host integration and tests.
22
+ Feature UI and model implementations must not import `@opetope/runtime`, another feature's implementation or their
23
+ own integration layer. Enforce these dependency boundaries with the host project's static checks.
24
+
25
+ ## 2. How to choose the shape (a checklist before `defineFeature`)
26
+
27
+ 1. What am I given from outside? The host → `defineHostContract` + `imports`. Another feature →
28
+ `imports: { x: feature }` (hard) or `optional(feature)` (may be absent or switch off). A capability by name →
29
+ `requires: { p: port }`.
30
+ 2. When do I live? Always → no `when`. Under a condition → `when: [condition]`; the condition is declared either by
31
+ the host (`defineCondition({ id })`) or by another feature (`defineCondition({ from, id, select })`).
32
+ 3. What do I do? Host calls → `calls(imports.x, [...])`; my own process → `call({ run, lane?, once?, singleFlight?,
33
+ policy?, within })`; data by key → `resource`; a stream → `stream` with `latest()`; external events → `event`;
34
+ a reaction to a change → `effect`; a subprocess under a condition → `scope.while`; a model with state →
35
+ `model(Decl, { source: imports.source }, (ctx, { source }) => …)`.
36
+ 4. What do I hand out? To other features → `exports` (`Call | Readable | Resource`, model fields, not the model).
37
+ To the application → `provides.port`. To the interface → `provides.slot/pipe/register`.
38
+ 5. UI? Only as a slot contribution. The component declares per-mount models through `requiresModels([...])`; the
39
+ `own` models are available on their own.
40
+
41
+ A feature whose implementation is heavy is written as a header and a body: the header keeps `id`, `imports`,
42
+ `requires`, a metadata `provides`, `when` and `body: () => import(...)`, and the body file writes
43
+ `defineFeature.body(header, { own, exports, provides })`. The application and consumers import the header only, and
44
+ the body loads when an instance opens (D186, D207). `defineFeature.preload(feature)` fetches that code early without
45
+ opening anything; on a feature with no body it is a successful no-op (D208).
46
+
47
+ Declaration stages (`own` and outer `provides`) see refs. Instance stages (`exports`, nested contribution
48
+ factories and model factories) receive materialized values. Compose model dependencies with a readonly map of
49
+ current-feature imports and call refs, including `requires.x`; optional imports keep their lookup projection.
50
+ Use `port(Port, { from: own.model, select: value => value.call })` for a model's call. `calls(imports.x, keys)`
51
+ accepts host methods, exported calls of a hard feature import, and exported calls over `optional(feature)`, where
52
+ they answer `CallError` `unavailable` while nothing provides them. Read the data of a weak edge with
53
+ `fromOptional(source, select, { missing })` instead of unfolding `lookup.kind` by hand (D187). For resources and streams, `target` selects a
54
+ `Readable<T | null | undefined>` and `retention` is optional; `scoped({ capacity })` fixes LRU. Effect filtering
55
+ uses `when(current, previous)`. These are the canonical shapes without compatibility aliases (D168, D169).
56
+
57
+ Declare a command with `ctx.call(options)` in a model or `call(options)` in `own`. Execute an existing command only
58
+ through `context.invoke(target, input)` in its execution context, or destructure it:
59
+ `run: (input, { invoke }) => invoke(deps.submit, input)`. When migrating, rename execution-context accesses,
60
+ destructuring and custom context types together; factory `call`, `calls` and the `Call` noun remain (D243).
61
+ The nested invocation inherits cancellation, authority and the lane stack; it does not choose a new policy.
62
+
63
+ A contribution's `when` is either a `Readable<boolean>` or a pure, synchronous predicate of the instance —
64
+ `({ exports, imports, own, read }) => boolean`. Its `read` only records what the answer depends on: do not write,
65
+ call or await inside it, and expect it to run again whenever a source it read changes. In that context `own` is
66
+ materialized, so a model field is a `Readable` and not the declaration ref (D220). A `pipe` declares its handler as
67
+ a descriptor with one key — `pipe(target, { fold: (value, meta, context) => next })` — whose `context` is that same
68
+ evaluation context. The `fold` runs only when the target folds: neither the declaration, nor
69
+ `defineFeature.preload`, nor the opening of the instance calls it, so keep the work that must happen on open in
70
+ `own` (D223).
71
+
72
+ ## 3. Forbidden — and what you will see
73
+
74
+ | Shape | Why not | Signal |
75
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
76
+ | `exports: ({ own }) => ({ m: own.model })` | a model is a record of values | typetest/`prepare`: `Feature export m must be a Call, a Readable or a Resource.` |
77
+ | `ctx.update(foreignReadable, v)` | the writer stays with the owner | a type error; at runtime `TypeError: Model update accepts only state created by this model context.` |
78
+ | `onDemand(feature)` | no such word since D105 | a type error and a `TypeError` in `feature-contract` |
79
+ | `optional(optional(x))` | one wrapper only | a type error and a `TypeError` |
80
+ | `when: 'authorized'` | only a `Condition` | a type error and a `TypeError` |
81
+ | a hard import where `provider.when ⊄ consumer.when` | the provider must live at least as long | the compiler: `… cannot depend on shorter-lived provider …; use optional for a weak edge.` |
82
+ | two providers of one port | one provider per application | the application compiler |
83
+ | `useModel(X)` of a per-mount contribution model in a component or hook without `requiresModels([X])` (nested ones included) | the type does not see the requirement; `own` models need no declaration (D158) | declaration review; runtime `missing` only if the model is absent from the frame |
84
+ | `throw` for a product outcome of a command | outcomes are values (D86) | `useCommand` shows `lastError` only for `failed`; `cancelled` is not an error |
85
+ | kernel words in public names (`Module`, `Attachment`, `Executor`, `Authority`, `Owner`, `Blueprint`, `IR`) and retired words (`Task`, `Signal`, `Domain`, `View`, `Setup`) | the author's vocabulary | the `ci:public-surface` gate |
86
+ | `createState`/`isCancellation` from `@opetope/core`, `useFeatureError` | off the safe entries (D142) | no such export; use `ctx.state`, and read cancellation through `useCommand` |
87
+ | wrapping every host method in `context.call` by hand | `context.calls(source, keys, { lane })` (D143) | — |
88
+ | `when: () => readable`, `when: async () => …`, `when: ({ read }) => read(counter)` | a predicate answers the fact, not a source, a promise or a truthy value (D220) | a type error; at runtime `TypeError: Feature contribution when predicate must return a boolean.` |
89
+ | `pipe(target, handler)` or `pipe(target, () => handler)` | a pipe takes a `{ fold }` descriptor (D223) | a type error; at runtime `TypeError: Feature pipe expects a descriptor: pipe(target, { fold: … }).` |
90
+
91
+ ## 4. Errors: read the `code`, not the text
92
+
93
+ `FeatureError` (`not-ready | retired | quarantined | cleanup-failed`), `CallError` (`cancelled | closed |
94
+ publication-rejected | unavailable`), `ReadableError` (`closed`), `ContributionError` (`missing | inactive | duplicate |
95
+ binding-invalid`), `ApplicationError` (`closed`), `DeclarationError` (`invalid-id`). Cancellation is a brand, not a class: `CallError` only with the codes `cancelled`/`closed`, `FeatureError` `retired`, `ContributionError` `inactive` (D138); `unavailable` and `publication-rejected` are product answers, and `useCommand` reports them as `failed` with `lastError`. The predicate `isCancellation` lives on `@opetope/core/internal` (D142) — a feature author does not need it. Closed state propagates through derived nodes: `derive` over a provider that is gone throws `closed` (D146).
96
+
97
+ Close a feature through `await instance.close()`. `FeatureError.retryCleanup` is optional and appears only for a
98
+ retryable quarantined frontier; check it before invoking. Other errors do not expose a no-op retry. Demand retry
99
+ belongs to its current source, and `Resource.retry()` is a separate resource operation (D168, D170).
100
+
101
+ A model's state belongs to its creating context, even when another model holds an `OwnedState` of the same type.
102
+ Register nothing after the lifetime fence. A failed factory must release its partial kernel, and cancellation
103
+ must not release the source before admitted work physically drains. For React effects, depend on destructured
104
+ `useCommand(...).run`, not the status-bearing result object. Mount props update as one snapshot in layout before
105
+ paint; the adapter and direct props follow the same rule (D170).
106
+
107
+ Declare `policy: 'latest'` in the model's `context.call` for an absolute value setter that must deliver the newest
108
+ input while a previous one waits; the consumer names no policy of its own (D185, D203). The default `queue` runs
109
+ every input in order and keeps button behaviour. A replaced waiting input gets `cancelled`, not a success or failure
110
+ callback; never declare `latest` for a sequence whose intermediate operations matter. A command's lane and physical
111
+ lifetime remain model responsibilities.
112
+
113
+ ## 5. Checks before commit
114
+
115
+ From the package: `npx tsc --noEmit`, `npm run ci:test`, `npm run ci:eslint`, `npm run ci:size-limit`.
116
+ In a consuming application, type-check the integration, run the affected feature tests and verify the production
117
+ build, chunk placement and application size budgets using that project's commands.
118
+ From the root: `npm run ci:eslint`, `npm run ci:oxlint`, `npm run ci:format`, `npm run ci:unused`,
119
+ `npm run ci:docs`.
120
+ From `tooling/stress`: `npm run ci:public-surface` on any change to a public name, `ci:type-stress`,
121
+ `ci:perf-memory`, `ci:inspection`.
122
+
123
+ ### Turn on `@opetope/lint`
124
+
125
+ The package ships the checkable part of the laws above, so a review does not have to spell them out. Register its
126
+ flat config on the files where features, models and their UI live:
127
+
128
+ ```js
129
+ // eslint.config.mjs
130
+ import opetope from '@opetope/lint';
131
+
132
+ export default [
133
+ {
134
+ files: ['src/**/*.{ts,tsx}'],
135
+ ...opetope.configs.recommended,
136
+ },
137
+ ];
138
+ ```
139
+
140
+ `recommended` holds the rules that need no knowledge of a host's directories. The rules, their fixes and the
141
+ boundary of each one are in [`../lint/README.md`](../../lint/README.md).
142
+
143
+ ## 6. Changing the framework, not a feature
144
+
145
+ - A public name or shape — only with a compile-checked pilot in a consuming application and a line in the decision log; do not
146
+ raise size or perf limits to hide a regression. An authorized vocabulary addition stays within the public-surface
147
+ target and updates its recorded budget with the decision (§5 of the spec).
148
+ - Files in `core/runtime/react/src` are ≤ 600 lines; split them by state machine, compiler, binding, or host
149
+ boundary.
150
+ - Hot path: no `Reflect.apply`/bind/argument arrays per call, no `Object.freeze` inside dispatcher loops, no `Error`
151
+ on a success path; a new allocation per operation only with a measurement.
152
+ - Internal names = the public words of the same concept; `module` is the kernel word for the unit of lowering, and
153
+ it stays in `internal`.
154
+
155
+ ## 7. Reviewing your own code (the seven lenses, briefly)
156
+
157
+ A law from §4 → where is its test; authority → nothing extra on a public entry; races → close during open, retire
158
+ during a call, a React commit later than a microtask; performance → allocations per call, O(N) per event; DX → the
159
+ words of §3 and typetests on the negatives, error texts that name the feature/instance/contribution and suggest the
160
+ fix; over-engineering → does the mechanism have a production caller; the human → the person who closes a gate is
161
+ not its author.
162
+
163
+ ## Data and commands in a component
164
+
165
+ `useModel(Declaration, (model, { read }) => ({ ... }))` selects data and command consumers in one hook (D205, D214).
166
+
167
+ A model selection can use a named interface without an index signature (D217). Its result is a flat data record;
168
+ arrays, functions, constructors and built-in collection/date/promise objects are not selection records.
169
+ The readonly inferred record maps authentic Call fields to `CommandHook`; other selected fields preserve their types.
170
+ `read(readable, project?)` reads explicitly, deduplicates source subscriptions and applies no automatic Resource retain.
171
+ Returned fields use `Object.is` equality; fresh nested objects are changed fields. The pure callback cannot call hooks,
172
+ commands or side effects. Subscription replacement and command admission happen at commit, not in an abandoned
173
+ render. Keys, aliases, local statuses and `.run` follow `useCommands`. No model creation, feature acquire or new
174
+ scheduler is implied. `useModel(Declaration)` keeps returning the granted model, with a constant hook layout when
175
+ selection changes. Sharing one hook is a DX choice, not a performance guarantee.
176
+
177
+ ## Scenario tests and physical activity
178
+
179
+ `createScenario(application, options)` from `@opetope/react/testing` opens the real application and its existing
180
+ inspection session (D206, D215). Supply the normal `imports`/`conditions` and a test-owned
181
+ `host.mount(Component)` adapter returning an `unmount()` handle. The package adds no DOM renderer or test-runner
182
+ dependency. `scenario.mount(target, { props })` uses the published Slot contributions and returns
183
+ `{ host, updateProps, unmount }`; `host` is the renderer's original result. Typed targets require `options.props`,
184
+ while targets without props omit it, exactly as with `Slot` (D217). Fixture commands do not bypass authority.
185
+
186
+ The synchronous constructor exposes `ready`, so a test can inspect a pending lazy body before readiness.
187
+ `waitFor(snapshot => predicate, { label, timeoutMs, pollIntervalMs })` wakes on inspection changes and also polls
188
+ external UI predicates; `notify()` wakes it after a controlled fixture update. The default deadline is 1000ms,
189
+ with a 10ms predicate poll. A `ScenarioTimeoutError` carries the data-only snapshot, bounded history and observed
190
+ conditions, feature phases, body loads, lane blockers and resource retention facts. It does not infer repository
191
+ or network causes. `getSnapshot()` and `history()` use that same observation model; history defaults to 64 snapshots,
192
+ activity to 256 records. Capacities accept integers from 1 to 10000. Do not replace predicates with a fixed number of ticks.
193
+
194
+ `close()` fences application admission synchronously, unmounts all registered screens and joins their cleanup with
195
+ physical application drain. Its deadline does not cancel cleanup: a later `close()` can await the same drain.
196
+ A readiness deadline likewise leaves the application available for inspection and explicit cleanup.
197
+ `ownership()` reports only registered runtime ownership, with `unknown` for missing, stale or truncated evidence;
198
+ a workspace stale snapshot is complete only after the scenario witnessed successful physical cleanup. This permits
199
+ a scoped zero-count assertion, without proving absence of arbitrary host, UI or GC leaks. Successful cleanup clears
200
+ application imports and internal renderer references. A failed cleanup promise can retain original errors and retry
201
+ capabilities; a caller that keeps `mounted.host` also keeps its own renderer result.
202
+
203
+ The inspection graph/frame schema is `/3`, with optional `opetope.runtime-activity/1` snapshots. Within one session,
204
+ a frame without `activity` preserves the previous activity; a full snapshot/reset without it clears that observation
205
+ (D216). Activity-bearing frames replace the previous activity in full.
206
+ Use matching runtime/devtools versions: `/2` readers reject the new revision. Activity identifies the execution,
207
+ actual feature generation, physical Calls, exact current lane blockers, registered resource leases and load attempts.
208
+ Host demand and UI models are unknown; stream observation covers state, not physical load identities. `freshness`
209
+ and `truncated` distinguish a complete live view from a partial or detached one. A closed session is stale;
210
+ `closed: true` requires successful physical application drain. No control authority or product payload is added.
211
+ Activity output is bounded by record capacity. Snapshot collection still visits registered owners, executors and
212
+ resources, so capacity does not bound traversal cost. Collection stops once truncation is proven;
213
+ idle executors may still require traversal to establish completeness. Normal call dispatch allocates no diagnostic record with
214
+ observation disabled. Graph frames remain bounded by the existing ring capacity.