{"_id":"@afyadigital/receitapro-engine","_rev":"12-59fffd97de53239d3ab28d8cb1b94edb","name":"@afyadigital/receitapro-engine","dist-tags":{"latest":"0.8.1"},"versions":{"0.0.1":{"name":"@afyadigital/receitapro-engine","version":"0.0.1","license":"MIT","_id":"@afyadigital/receitapro-engine@0.0.1","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"01f02d6b1836b256579d0cfed592b9b2b88735a7","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.0.1.tgz","fileCount":13,"integrity":"sha512-jNbXR6yvAhfvXEzPWbAcwDH9tdhhKfi5VBGjIiK5qC7J01qos87cPYKIaLR72vuh1n2OiNr1Q5WG4xAUHoUNhA==","signatures":[{"sig":"MEQCIBXHQZACTNO+ZbrlbhDiSlLs03FEWHdG+8SBNWvQrK7zAiA3fTxoIEdFj4dOaWvRhjCp8mOgYixJ/YYSSu6WiAHd/g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":67396},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.0.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/d6c1421374c8d9d8f499e6a9a4e052de/afyadigital-receitapro-engine-0.0.1.tgz","_integrity":"sha512-jNbXR6yvAhfvXEzPWbAcwDH9tdhhKfi5VBGjIiK5qC7J01qos87cPYKIaLR72vuh1n2OiNr1Q5WG4xAUHoUNhA==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.0.1_1781620459411_0.7998839321704831","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@afyadigital/receitapro-engine","version":"0.1.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.1.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"0651f9217249f0251d9f8cfb1d169eadf5c5d64a","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.1.0.tgz","fileCount":13,"integrity":"sha512-CzCdnLXXIWTsQUl+Pzv+Qm3BLgai7Yf4+GFpHKtTHaYI+FIg6YQOgSJu0fJU7H7tXoqtY4BTrkTEqIizgr2w8A==","signatures":[{"sig":"MEUCIQCynDUPgOndLo9u8r+rw8mLtK5Im6BZEYRGVhTD5Oz2MAIgNUKAGD/q5+1E//ypWt+MRO23e+dBIxkpjTL5g10XXaA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84761},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/e08269c2d7fb12568dc116dcbc8206d4/afyadigital-receitapro-engine-0.1.0.tgz","_integrity":"sha512-CzCdnLXXIWTsQUl+Pzv+Qm3BLgai7Yf4+GFpHKtTHaYI+FIg6YQOgSJu0fJU7H7tXoqtY4BTrkTEqIizgr2w8A==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.1.0_1787832047474_0.6400376260263052","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@afyadigital/receitapro-engine","version":"0.2.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.2.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"0d3787bfe5e3263097e67d9e4864a36a942c2d87","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.2.0.tgz","fileCount":13,"integrity":"sha512-ci0lrdkVGI541NQLguk7dslkH6m8VgrzZL1QTzZ7LKGW8z1EvhwBEFJdCvLl8GXTJoYgqmiCFOVa1EBKIQHIXg==","signatures":[{"sig":"MEQCIDWf/v2yv3Gb3HPDlO/dFPf67Q2/4diCcqx8zrCu8O61AiBKB+sQGz0WR3mn1KH2oaVgKePpKXiipC7a16Rls9ttgg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":101849},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.2.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/bf90462dac2120438b9596917dff88a7/afyadigital-receitapro-engine-0.2.0.tgz","_integrity":"sha512-ci0lrdkVGI541NQLguk7dslkH6m8VgrzZL1QTzZ7LKGW8z1EvhwBEFJdCvLl8GXTJoYgqmiCFOVa1EBKIQHIXg==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.2.0_1788178235478_0.7731517007429436","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@afyadigital/receitapro-engine","version":"0.3.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.3.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"985d87295d1d8d587da69604a85e87412bb3a88e","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.3.0.tgz","fileCount":13,"integrity":"sha512-ihHowXQEmIVoQpqMGxP+oIk+kxu4VxzX5WKFoFWtoE/3I6CKJDPVApuvQBicUiP+aci658MtgG426Zd2lJdMNg==","signatures":[{"sig":"MEYCIQCNMauFq0QjR34bSBUK5so/AUyub7PBq5jVcvpgnSUg/gIhAKDyNQW1hsZSU4zSCfaPh2xRJhgjufVyvOgJFSTw5S6+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":110915},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.3.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/3ba60ce12590c5189bd94bea661c8772/afyadigital-receitapro-engine-0.3.0.tgz","_integrity":"sha512-ihHowXQEmIVoQpqMGxP+oIk+kxu4VxzX5WKFoFWtoE/3I6CKJDPVApuvQBicUiP+aci658MtgG426Zd2lJdMNg==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.3.0_1788186806503_0.4568568847097505","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@afyadigital/receitapro-engine","version":"0.3.1","license":"MIT","_id":"@afyadigital/receitapro-engine@0.3.1","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"daa2507e89ed23597ff9fca0cca799a6187302e3","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.3.1.tgz","fileCount":13,"integrity":"sha512-vTctyaADtIsovDj90Ht60s4LGG0s5r8UbS80x9LLJ3BZdF0IcqpAwnr84OwNznytkVPs93OSV5J8jtmWecE3Rg==","signatures":[{"sig":"MEUCIQC7ybpwuv4FKCWlzx+AiN5AywNHzHlTY0V0E/67kIWD6QIgW2Yul5FAd+yoA9h0My4K59bZeDug/sQjOC3d8mDWZqQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":110154},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.3.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/9a61c787292daf1d7299573885044e8d/afyadigital-receitapro-engine-0.3.1.tgz","_integrity":"sha512-vTctyaADtIsovDj90Ht60s4LGG0s5r8UbS80x9LLJ3BZdF0IcqpAwnr84OwNznytkVPs93OSV5J8jtmWecE3Rg==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.3.1_1788187744956_0.7968627552158578","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@afyadigital/receitapro-engine","version":"0.4.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.4.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"e249fdf9b68ca103545f3488c373f095868cda0f","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.4.0.tgz","fileCount":14,"integrity":"sha512-VzpHnvuDor78H1VTWSWO+FFbJ6Rw2rEzFYlfimFAsqMfCVk2WzCZDW5tgfcaGERSeFXalDjRv3UNLxFE7YwiSg==","signatures":[{"sig":"MEUCIQDARqBr0bk/NsGW2X7ZaCkd3oS856lHVyIxp2li1afZSwIgcaEPWwjVCmyctIhqTrg9PTjI5ahDJN6GFfcW/ys8IVs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":133968},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.4.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/daa042087c0c2fee517e1bb4166c8e46/afyadigital-receitapro-engine-0.4.0.tgz","_integrity":"sha512-VzpHnvuDor78H1VTWSWO+FFbJ6Rw2rEzFYlfimFAsqMfCVk2WzCZDW5tgfcaGERSeFXalDjRv3UNLxFE7YwiSg==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.4.0_1788287242446_0.42041271663060553","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@afyadigital/receitapro-engine","version":"0.5.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.5.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"1419437a0f0be0b5ba6b99f05ac64d7e40b7a288","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.5.0.tgz","fileCount":14,"integrity":"sha512-ryKxsnveEFoRTSkl29icX7zAsHfm/GsW7TXC3MjWmKtaH8sPJC+n+ofTQvT8Uw6RLP9QdQRj8NCDkoHYphdqXA==","signatures":[{"sig":"MEUCID5vD/mP9tvrxUiw5Zgtw1AELiPjvCnRMPRv4XdMRcyjAiEA9nG/524TXMfoauWCDtk3iAdghgB6bY1kmfj6n6qCxng=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":168428},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.5.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/8518b7896257436c5b8146045485cffd/afyadigital-receitapro-engine-0.5.0.tgz","_integrity":"sha512-ryKxsnveEFoRTSkl29icX7zAsHfm/GsW7TXC3MjWmKtaH8sPJC+n+ofTQvT8Uw6RLP9QdQRj8NCDkoHYphdqXA==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.5.0_1788460242302_0.4379382164807981","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@afyadigital/receitapro-engine","version":"0.6.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.6.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"1e9f0402f5d3b06f70bcb0fef194fc424a6f6272","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.6.0.tgz","fileCount":14,"integrity":"sha512-FhQp7/8KRCE/r9zYp0inYKm4QM9UGXWAxadba5mIG0ONgSL7a0AZBbmgIWDdEAaOvwem0MvXPkHWhDx7d2EvCw==","signatures":[{"sig":"MEUCIQCd3PeA8qFMyg4haEsr7FUPzKobM4hUsPYI/9m3RQhAhQIgX50/4i7ufvwu+8OY4EuGxV0i0eHS05slv1Jde4SPUOQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":182254},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.6.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/baff7b310735509c9ca7bf3b57a8b388/afyadigital-receitapro-engine-0.6.0.tgz","_integrity":"sha512-FhQp7/8KRCE/r9zYp0inYKm4QM9UGXWAxadba5mIG0ONgSL7a0AZBbmgIWDdEAaOvwem0MvXPkHWhDx7d2EvCw==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.6.0_1788960498260_0.04505034156000276","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@afyadigital/receitapro-engine","version":"0.7.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.7.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"fbe3e00539bc5888ea4daceb7e5b918f117ac33c","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.7.0.tgz","fileCount":14,"integrity":"sha512-hyq8j1KJTonh91CfRBKjWOoNZ0itlQpElBUaI5Y12dUNB6uREeZ5CzMATWdUDz1M7Hai6bJVhpWqDOXnhpeZ1g==","signatures":[{"sig":"MEUCIQCGg+PkI0za4dOKBmVKIz64VksE6fQd8HHU3c52GgVfjgIgBLsdVJFCbbSOlOL/EHOuLvgBcb0qfRjx3P+YtLm+Aog=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":188206},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.7.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"adnts","email":"andreolle18@gmail.com"},"prettier":"@rx/config-prettier","_resolved":"/tmp/bf2892db17ecd0749ba9106b191584cd/afyadigital-receitapro-engine-0.7.0.tgz","_integrity":"sha512-hyq8j1KJTonh91CfRBKjWOoNZ0itlQpElBUaI5Y12dUNB6uREeZ5CzMATWdUDz1M7Hai6bJVhpWqDOXnhpeZ1g==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.13.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.7.0_1789388108173_0.7146069635385248","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@afyadigital/receitapro-engine","version":"0.8.0","license":"MIT","_id":"@afyadigital/receitapro-engine@0.8.0","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"homepage":"https://github.com/iclinic/receitapro-frontend#readme","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"c83ef463f54f4e33b3769ecf2b19dd95ec5700cf","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.8.0.tgz","fileCount":3,"integrity":"sha512-OTbfsJ2vqQHQGd3qvd6CIur6AqQ93pzmyay1I1/gD/2rLvvHG6JQoatdJluY4HZsww16gIz4wPHOBgc8uJlfDA==","signatures":[{"sig":"MEUCIGNnmGziH+6sI9L8fT0teSkINXIBOnX0+6VdO/5h4OWTAiEAw7bqr1KHPFA6F6I/GK2YpBpTch16f7ibt/ajP6XBFbU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIEdbVYbMtTEviw457et2Wca6hLzpTtdyXDL6gUbYK77BAiB+zXvWYUoM2GrHHntrUm0I/6FAUakz1XmGrDUbE/OXzg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108333},"main":"./dist/index.js","type":"module","_from":"file:afyadigital-receitapro-engine-0.8.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"_npmUser":{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},"prettier":"@rx/config-prettier","_resolved":"/private/var/folders/k2/rrp0c5d96gngx_xrfryk3sz00000gq/T/2ef797593fcf241465e01eb6e9113c9f/afyadigital-receitapro-engine-0.8.0.tgz","_integrity":"sha512-OTbfsJ2vqQHQGd3qvd6CIur6AqQ93pzmyay1I1/gD/2rLvvHG6JQoatdJluY4HZsww16gIz4wPHOBgc8uJlfDA==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.16.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/receitapro-engine_0.8.0_1790271800773_0.4434446353932975","host":"s3://npm-registry-packages-npm-production"}},"0.8.1":{"_id":"@afyadigital/receitapro-engine@0.8.1","bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"dist":{"shasum":"5cbd2689c32719877fc83c431fd2fb87030944d6","tarball":"https://registry.npmjs.org/@afyadigital/receitapro-engine/-/receitapro-engine-0.8.1.tgz","fileCount":14,"integrity":"sha512-WkMANbkqHVlQTCWy9AQW969W6KAmLAej5dohPk4gFoO7+aQaKeOXHTWebAEaVGlI8glNoA4DSFcmgnmJQNDbyw==","signatures":[{"sig":"MEUCIQC+49DdFgD0Vm7WrRjz1Ybku4Zz04xU2tnX3HB/RvtN/wIgGQxp7aDfB8MvXt+j9iw8F7nR/3z+o3sqaBMkPtz2UP8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCTyeBkOLMKztdvOa0obcaTgoe42N9vMVXOnebNe6YTsgIgS5yZ2JogQAZC7mBArmXp5cZmki9F8ii/yzNUG6VPewY="}],"unpackedSize":187301},"main":"./dist/index.js","name":"@afyadigital/receitapro-engine","type":"module","_from":"file:afyadigital-receitapro-engine-0.8.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"license":"MIT","scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","format":"prettier --write \"src/**/*.ts\"","dev:mock":"vite build --watch","lint:fix":"eslint . --fix","test:watch":"vitest","type-check":"tsc"},"version":"0.8.1","_npmUser":{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},"homepage":"https://github.com/iclinic/receitapro-frontend#readme","prettier":"@rx/config-prettier","_resolved":"/private/var/folders/k2/rrp0c5d96gngx_xrfryk3sz00000gq/T/7b8ab463c611933b95c0d9c62c419cfb/afyadigital-receitapro-engine-0.8.1.tgz","_integrity":"sha512-WkMANbkqHVlQTCWy9AQW969W6KAmLAej5dohPk4gFoO7+aQaKeOXHTWebAEaVGlI8glNoA4DSFcmgnmJQNDbyw==","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"_npmVersion":"11.16.0","description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","directories":{},"maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.16.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0","jsdom":"^25.0.0","eslint":"^9.0.0","vitest":"^3.2.4","prettier":"^3.8.3","typescript":"^5.6.2","@types/node":"^22.0.0","@rx/contracts":"0.0.1","vite-plugin-dts":"^4.0.0","@rx/config-eslint":"0.0.1","typescript-eslint":"^8.59.1","@rx/config-prettier":"0.0.1","@vitest/coverage-v8":"^3.2.4","@rx/config-typescript":"0.1.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/receitapro-engine_0.8.1_1790695495975_0.43358335017636573"}}},"time":{"created":"2026-06-16T14:34:19.280Z","modified":"2026-09-29T15:24:56.292Z","0.0.1":"2026-06-16T14:34:19.543Z","0.1.0":"2026-08-27T12:00:47.619Z","0.2.0":"2026-08-31T12:10:35.677Z","0.3.0":"2026-08-31T14:33:26.642Z","0.3.1":"2026-08-31T14:49:05.106Z","0.4.0":"2026-09-01T18:27:22.596Z","0.5.0":"2026-09-03T18:30:42.443Z","0.6.0":"2026-09-09T13:28:18.408Z","0.7.0":"2026-09-14T12:15:08.308Z","0.8.0":"2026-09-24T17:43:20.852Z","0.8.1":"2026-09-29T15:24:56.113Z"},"bugs":{"url":"https://github.com/iclinic/receitapro-frontend/issues"},"license":"MIT","homepage":"https://github.com/iclinic/receitapro-frontend#readme","repository":{"url":"git+https://github.com/iclinic/receitapro-frontend.git","type":"git","directory":"packages/integrations/engine"},"description":"RxEngine — host-side integration for embedding @receitapro/web via iframe + MessageChannel","maintainers":[{"name":"hennrique.breno","email":"brenohennrique@icloud.com"},{"name":"rodrigodocarmo94","email":"rodrigodocarmo94@icloud.com"},{"name":"tiagocosmai","email":"tiago.cosmai@afya.com.br"},{"name":"adnts","email":"andreolle18@gmail.com"}],"readme":"# @afyadigital/receitapro-engine\n\nSDK do lado do **host** para embarcar o produto de prescrição `@receitapro/web` num iframe — pense em Stripe Elements ou no widget do Intercom: o integrador adiciona uma `<div>` ao layout, instancia o engine e o restante é cuidado pela própria Afya.\n\nO engine cuida de três coisas, e somente estas:\n\n1. **Renderiza o iframe** apontando para a URL pública do produto (resolvida por ambiente — `production` / `staging` / `development`). A URL nunca é exposta ao integrador.\n2. **Estabelece um canal privado** via `MessageChannel`: faz o handshake `engine:mount` — que já leva o `token` e o `initialValue` —, transfere o `port2` ao iframe e passa a relaiar tudo pelo `port1`. Após esse passo, **nenhuma mensagem do produto trafega por `window.postMessage`** — o canal é invisível para scripts da página do host.\n3. **Expõe uma API `Promise`-based** para enviar commands (`update:token`, `update:patient`) e um `EventEmitter` para escutar eventos de ciclo de vida (`app:init`, `app:ready`, `token:expired`, `prescription:created`, `app:error`). O que cada command faz hoje do lado do produto está em [Catálogo de commands](#catálogo-de-commands).\n\n> **A credencial vai na montagem.** O `token` do construtor é transportado pelo `engine:mount`, então o caminho feliz não exige command nenhum do host: `render()` e o iframe já sobe autenticado. `commands.update('token', …)` cobre os dois casos posteriores — renovação em sessão e correção, quando a validação recusa o token da montagem.\n\n### O que o engine **não** faz\n\n- **Não valida o conteúdo de payloads.** É transporte puro: não importa Zod, não conhece DTOs, não sabe o que é \"medication\" ou \"vaccine\". Toda validação de domínio vive no `@receitapro/web`. A única regra que o engine conhece é **estrutural**, olha presença e não conteúdo, e existe porque falharia silenciosamente do outro lado: `initialValue.appointment` exige `initialValue.patient` (ver [`appointment`](#appointment--atendimento-e-contexto)).\n- **Não enfileira commands** antes do `render()` ou após `unmount()` — falhas explícitas em vez de bugs silenciosos.\n- **Não conhece o host** além do `token` opaco que recebe. Não restringe a origin do integrador — o produto é `embed-from-any-origin` (ver [Segurança](#segurança)).\n\n### Modelo conceitual\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant Host as Host (sua aplicação)\n    participant Engine as RxEngine\n    participant Iframe as iframe (rx-web)\n\n    Host->>Engine: new RxEngine({ container, token,<br/>initialValue? })\n    Host->>Engine: render()\n    Engine->>Iframe: iframe.src = IFRAME_URL<br/>(carrega rx-web)\n    Engine->>Iframe: window.postMessage('engine:mount',<br/>{ token, initialValue }, [port2])\n    Note over Engine,Iframe: Único window.postMessage do contrato —<br/>após isto, tudo pelo MessageChannel privado<br/>(port1 ↔ port2, cross-origin, isolado da window)\n\n    Host->>Engine: await commands.update('patient', payload)\n    Engine->>Iframe: port1.postMessage({ type, id, payload })\n    Iframe-->>Engine: port2.postMessage({ event, id, payload })\n    Engine-->>Host: Promise<Result> resolve(payload)\n\n    Iframe-->>Engine: { event: 'app:init' } / 'app:ready' /<br/>'token:expired' / 'prescription:created' / 'app:error'\n    Engine-->>Host: engine.events.listen(...) — ciclo de vida\n\n    Host->>Engine: unmount()\n    Engine->>Iframe: port1.close() + iframe.remove()\n```\n\nO `id` que carrega cada envelope no fio (`{ type, id, payload }`) é **detalhe interno de transporte** — o engine o usa para casar request/response e nunca o expõe na API pública. Para o host, `commands.add(...)` é uma `async function` qualquer.\n\n---\n\n## Instalação\n\nDentro do monorepo:\n\n```bash\npnpm --filter <consumer> add @afyadigital/receitapro-engine\n```\n\nEm hosts externos (após publicação):\n\n```bash\nnpm install @afyadigital/receitapro-engine\n```\n\n---\n\n## Uso básico\n\n```ts\nimport type { AppReadyPayload } from '@afyadigital/receitapro-engine'\nimport { RxEngine } from '@afyadigital/receitapro-engine'\n\nconst container = document.getElementById('rx-container')!\n\nconst engine = new RxEngine({\n  container,\n  token: physicianToken, // access_token CRU, sem o prefixo `Bearer` — ver abaixo\n})\n\n// Registre os listeners ANTES de `render()`. Os eventos chegam em callbacks do\n// `load` do iframe: quem registra depois pode perder o evento e ficar esperando\n// para sempre por algo que já aconteceu.\n//\n// Nenhum listener é obrigatório para a prescrição abrir — o token e o\n// `initialValue` foram no handshake. `app:init` só interessa a quem vai enviar\n// commands: é o marco a partir do qual o canal os aceita.\nengine.events.listen<AppReadyPayload>('app:ready', (e) => {\n  if (e.payload?.tokenValid === false) {\n    // O token entregue na montagem foi recusado pelo BFF. Como o host não tem como\n    // validá-lo antes de entregar, uma tentativa de correção é legítima: reemita e\n    // mande por `commands.update('token', novoToken)`. Recusado de novo, é credencial\n    // errada — corrija a emissão em vez de insistir.\n    // Para o token que foi aceito e venceu, o sinal é `token:expired`.\n  }\n})\n\nengine.render()\n```\n\n### Duas regras que respondem pela maior parte do suporte\n\n1. **Registre os listeners antes de `render()`.** `render()` insere o iframe e o `app:init` pode chegar antes da próxima linha do seu código. Um `listen(…)` registrado depois nunca dispara — o sintoma é \"a prescrição abre e o host nunca fica sabendo de nada\", inclusive de um `app:ready` com `tokenValid: false` ou de um `token:expired` que ele precisaria atender.\n2. **Envie o `access_token` cru, sem `Bearer`.** O produto embarcado monta o header `Authorization: Bearer <token>` sozinho. Passar `` `Bearer ${access_token}` `` produz `Authorization: Bearer Bearer eyJ…` e **todas** as chamadas voltam `401` — a tela abre e falha logo em seguida. Vale para os dois lugares em que o token aparece: `RxEngineConfig.token` e `commands.update('token', …)`.\n\n> O `token` do construtor é **obrigatório** e é **transportado** pelo `engine:mount`: é a entrega, não uma cópia decorativa. Até a 0.5.0 o campo era validado na construção e descartado, e a credencial só chegava se o host disparasse `update:token` no `app:init` — quem preenchia a config e esquecia o command montava um iframe sem credencial, sem receber sinal nenhum de que a montagem nunca ia concluir.\n\n---\n\n## Uso com `initialValue` (pré-hidratação)\n\n`initialValue` é entregue no payload da mensagem interna `engine:mount`, junto com a transferência do `port2`. Ele é processado pelo iframe **antes** do primeiro render do React, eliminando o flash de tela vazia e a necessidade de despachar uma sequência de commands após o `app:init`.\n\n```ts\nimport { RxEngine } from '@afyadigital/receitapro-engine'\n\nconst engine = new RxEngine({\n  container: document.getElementById('rx-container')!,\n  token: physicianToken, // cru, sem `Bearer`\n  initialValue: {\n    appointment: { id: 'atd-001' },\n    patient: { id: 'pac-001', name: 'Maria Silva', cpf: '05180005329' },\n  },\n})\n\nengine.render()\n```\n\n> **Hoje o `initialValue` hidrata `patient` e `appointment`.** As chaves irmãs `medications`, `vaccines` e `exams` são campos **reservados** — em breve: já existem no contrato de tipos e são aceitas sem erro, mas ainda não são consumidas, e a prescrição abre sem os itens. Enviá-las é inofensivo, só não produz efeito. Do lado dos commands o recorte é ainda mais estrito: qualquer escopo fora de `update:token` / `update:patient` é recusado com `app:error` (`unsupported_command`).\n\n> O engine não inspeciona `initialValue` em runtime (transporte puro), mas o campo é **tipado**: autocomplete e documentação de cada atributo aparecem direto no literal passado ao construtor, sem precisar anotar nada. Campos ainda não previstos no contrato continuam aceitos e passam intactos. O tipo `InitialValue` também pode ser importado do próprio `@afyadigital/receitapro-engine` — que **re-exporta** de `@rx/contracts` (dependência transitiva, host não instala separadamente) — quando o host quer declarar o objeto separado da chamada. Tipos são apagados no build; o bundle runtime do engine permanece sem referência a `@rx/contracts`.\n>\n> ```ts\n> import type { InitialValue } from '@afyadigital/receitapro-engine'\n> import { RxEngine } from '@afyadigital/receitapro-engine'\n>\n> const initialValue: InitialValue = {\n>   appointment: { id: 'atd-001' },\n>   patient: { id: 'pac-001', name: 'Maria Silva' },\n> }\n> ```\n\n### `patient` — o que o host pode pré-preencher\n\n`initialValue.patient` cobre o cadastro que o host já tem. Só `id` e `name` são obrigatórios; todo o resto é opcional.\n\n```ts\npatient: {\n  id: 'pac-001',           // id NO SEU sistema — chega ao produto como externalId\n  name: 'Maria Silva',     // nome de exibição; vira nome social se diferir de civilName\n  cpf: '051.800.053-29',   // com ou sem máscara\n  civilName: 'Maria Clara Gomes Silva',\n  motherName: 'Ana Silva',\n  email: 'maria@example.com',\n  phone: '+5511999998888', // ou 11999998888\n  birthdate: '1990-05-20', // ISO\n  sex: 'female',\n  weight: '64,5',          // número em texto, vírgula ou ponto\n  height: '1,70',\n  address: {               // tudo-ou-nada; omita o bloco se estiver incompleto\n    cep: '01001-000',\n    state: 'SP',\n    city: 'São Paulo',\n    street: 'Praça da Sé',\n    neighborhood: 'Sé',\n    number: '100',\n    complement: 'apto 12', // único campo opcional do bloco\n  },\n  lme: { /* ver \"O bloco lme\" abaixo — tudo-ou-nada também */ },\n}\n```\n\n#### Campos de `patient`\n\n| Campo        | Tipo             | Obrigatório | Observações                                                                                                                                     |\n| ------------ | ---------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |\n| `id`         | `string`         | ✓           | Id **no seu sistema**, **até 36 caracteres**; chega ao produto como `externalId`                                                                |\n| `name`       | `string`         | ✓           | Nome de exibição; vira nome social quando difere de `civilName`                                                                                 |\n| `cpf`        | `string`         | —           | Com ou sem máscara. **Validado**: CPF inválido invalida o payload inteiro                                                                       |\n| `civilName`  | `string`         | —           | Nome de registro, quando difere do de exibição                                                                                                  |\n| `motherName` | `string`         | —           | —                                                                                                                                               |\n| `email`      | `string`         | —           | Validado como e-mail                                                                                                                            |\n| `phone`      | `string`         | —           | Com ou sem máscara/DDI. Número incompleto invalida o payload                                                                                    |\n| `birthdate`  | `string`         | —           | **Validado**: ISO `YYYY-MM-DD`, dia real, não-futura; `'20/05/1990'` invalida o payload. Sufixo de hora (`'…T00:00:00Z'`) é aceito e descartado |\n| `sex`        | `string`         | —           | O formulário embarcado só reconhece `'female'` e `'male'`; outros valores ficam sem seleção                                                     |\n| `weight`     | `string`         | —           | Número **em texto**, kg, vírgula ou ponto (`'64,5'`)                                                                                            |\n| `height`     | `string`         | —           | Número **em texto**, metros (`'1,70'`)                                                                                                          |\n| `address`    | `PatientAddress` | —           | Bloco **tudo-ou-nada**                                                                                                                          |\n| `lme`        | `PatientLme`     | —           | Bloco **tudo-ou-nada**                                                                                                                          |\n\n#### O bloco `address`\n\nTudo-ou-nada: enviar `address` implica enviar todos os campos obrigatórios. Sem o endereço completo, omita o bloco inteiro.\n\n| Campo          | Tipo     | Obrigatório | Observações                   |\n| -------------- | -------- | ----------- | ----------------------------- |\n| `cep`          | `string` | ✓           | 8 dígitos, com ou sem máscara |\n| `state`        | `string` | ✓           | Sigla de 2 letras (`'SP'`)    |\n| `city`         | `string` | ✓           | —                             |\n| `street`       | `string` | ✓           | Logradouro, sem número        |\n| `neighborhood` | `string` | ✓           | —                             |\n| `number`       | `string` | ✓           | Texto — aceita `'s/n'`        |\n| `complement`   | `string` | —           | Único campo opcional do bloco |\n\n#### O bloco `lme`\n\nDados exclusivos do Laudo de Medicamento Especializado. Opcional como conjunto, mas também **tudo-ou-nada** — e com duas regras condicionais validadas no recebimento.\n\n| Campo             | Tipo                                                           | Obrigatório                    | Observações                                                                              |\n| ----------------- | -------------------------------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------- |\n| `document`        | `{ type: 'cpf' \\| 'cns'; value: string }`                      | ✓                              | `value` com ou sem máscara                                                               |\n| `skinColor`       | `'branca' \\| 'preta' \\| 'parda' \\| 'amarela' \\| 'indigena'`    | ✓                              | Raça/cor autodeclarada                                                                   |\n| `isCapable`       | `boolean`                                                      | ✓                              | Capacidade civil do paciente                                                             |\n| `filledBy`        | `{ responsible: LmeResponsible; name?: string; cpf?: string }` | ✓                              | `responsible`: `'patient' \\| 'patientMother' \\| 'responsible' \\| 'physician' \\| 'other'` |\n| `ethnicity`       | `string`                                                       | **se `skinColor: 'indigena'`** | Etnia indígena declarada                                                                 |\n| `responsibleName` | `string`                                                       | **se `isCapable: false`**      | Nome do responsável legal                                                                |\n\n```ts\nlme: {\n  document: { type: 'cpf', value: '05180005329' },\n  skinColor: 'indigena',\n  ethnicity: 'Guarani',      // obrigatório porque skinColor é 'indigena'\n  isCapable: false,\n  responsibleName: 'Ana Silva', // obrigatório porque isCapable é false\n  filledBy: { responsible: 'responsible', name: 'Ana Silva', cpf: '05180005329' },\n}\n```\n\n> As condicionais são **opcionais no tipo** e obrigatórias na validação: o TypeScript não acusa, mas o payload é reprovado no recebimento (`invalid_initial_value` na montagem, `invalid_patient` no command) e nada é hidratado.\n\nPontos que mudam para quem vinha do widget legado:\n\n- **`address` é objeto, não string.** O produto embarcado trabalha com endereço em campos separados; aceitar uma string exigiria parsing heurístico. Quebre a string nos campos de `PatientAddress`.\n- **`age` saiu do contrato.** Nunca existiu no legado e não era consumido. Envie `birthdate`.\n- **`patient.id` é o id no seu sistema**, não o id interno do produto. Ele chega como `externalId` e é a chave pela qual as edições feitas na tela voltam para você, pelo prescription-bff.\n\nO mesmo formato vale depois da montagem: sem `appointment`, o host troca ou remove o paciente em runtime com [`commands.update('patient', …)`](#trocar-o-paciente-depois-da-montagem--updatepatient), sem remontar o iframe.\n\n> Payload inválido nunca derruba o iframe: nada hidrata, o host recebe `app:error` com `type: 'invalid_initial_value'` e a lista de `issues`, e a UI segue operável no fluxo normal de seleção de paciente.\n\n### `appointment` — atendimento e contexto\n\n`initialValue.appointment` identifica o atendimento em que a prescrição acontece. Fica na **raiz** de `initialValue`, porque é contexto da prescrição e não atributo do paciente. Serve para amarrar a receita ao atendimento aberto no seu sistema — é rastreabilidade, e **não** muda nada na UI.\n\nQuem coloca a prescrição em **modo gerenciado** é o `patient`: mandar o paciente pela integração é dizer que a escolha é sua, e a UI embarcada deixa de oferecer _Trocar paciente_ e _Selecionar_. Isso impede a divergência entre o paciente aberto no seu sistema e o paciente da prescrição emitida.\n\n```ts\n// Modo gerenciado: basta o `patient` para a seleção ficar travada na UI.\ninitialValue: {\n  patient: { id: 'pac-001', name: 'Maria Silva' },\n}\n\n// Com atendimento: mesma trava, mais o contexto do atendimento na receita.\n// `patient` é obrigatório sempre que `appointment` for enviado.\ninitialValue: {\n  appointment: { id: 'atd-001' },\n  patient: { id: 'pac-001', name: 'Maria Silva' },\n}\n```\n\n#### O que o host envia × o que acontece na UI\n\n| O que o host envia                   | O que acontece na UI                                                                                                                                                                             |\n| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `patient` (com ou sem `appointment`) | **Modo gerenciado**: o paciente fica travado, _Trocar paciente_ e _Selecionar_ somem da tela, _Editar_ continua disponível                                                                       |\n| `appointment` sem `patient`          | **Recusado**: não compila em TypeScript, `Error` no construtor e — se o config for mutado depois — `render()` vira no-op com `app:error` (`appointment_without_patient`). Nenhum iframe é criado |\n| `patient` inválido                   | Nada hidrata, o modo gerenciado **não** ativa, a UI segue operável na seleção normal e o host recebe `app:error` (`invalid_initial_value`)                                                       |\n| `initialValue` ausente ou `null`     | Prescrição em branco, no fluxo normal de seleção de paciente. Não é erro e não emite nada                                                                                                        |\n\nO modo gerenciado não é uma flag: ele é **derivado** da origem do paciente em tela. É por isso que um paciente reprovado na validação não trava nada — sem paciente hidratado não há o que travar. E a trava é da **UI do médico**, não do canal: você continua trocando ou limpando o paciente por `commands.update('patient', …)` (ver [`commands.update`](#commandsupdatescope-payload)).\n\n> **Enviar `appointment` sem `patient` é erro.** Um atendimento sem paciente é um contexto pela metade — não há a quem amarrar a receita. Como não há estado válido a montar, a prescrição **não chega a carregar**:\n>\n> - em TypeScript, **não compila** — o par está no tipo `InitialValue`, então o editor acusa na hora, apontando o `patient` que falta;\n> - `new RxEngine({ … })` **lança `Error`** — a checagem de runtime segue valendo para hosts em JavaScript ou que contornem o tipo com um `as`; envolva a construção em `try/catch`, como você já faz para `container` e `token`;\n> - se o `initialValue` for invalidado depois da construção, o `render()` vira **no-op** (nenhum iframe é inserido) e o host recebe `app:error` com `type: 'appointment_without_patient'`.\n>\n> Os dois caminhos de runtime também gritam um **`console.error`** nomeando o campo que falta, em qualquer ambiente. É de propósito: o `Error` costuma acabar engolido pelo `try/catch` de quem só quer mostrar uma tela de erro, e o `app:error` depende de haver listener — sem o log, o sintoma que sobra é \"a prescrição não abre\". O log nunca inclui o `initialValue`, que carrega dados do paciente.\n>\n> **O paciente muda em runtime**, com ou sem atendimento: `commands.update('patient', …)` troca ou limpa (`payload: null`) sem remontar o iframe. O atendimento em si não muda por command — ele só entra pela montagem.\n>\n> _Editar_ o cadastro do paciente **continua disponível** em modo gerenciado. O que o host gerencia é a _escolha_ do paciente, não o cadastro. A edição é gravada no prescription-bff pela chave `external_id` do próprio host — hosts que precisem reagir a ela devem observar o prescription-bff, não o `RxEngine`.\n>\n> O legado usava a grafia `apointment`. O contrato adota `appointment`, com dois `p`.\n\n---\n\n## Ciclo de vida\n\n```mermaid\nflowchart TD\n    A[new RxEngine&#40;config&#41;] --> B[render&#40;&#41;]\n    B --> C[iframe inserido no DOM]\n    C -->|load event| D[engine:mount + port2<br/>host → iframe via window.postMessage]\n    D --> E[iframe hidrata stores<br/>e emite app:init]\n    E --> F[engine.events.dispatch<br/>→ host listener]\n    F --> G[canal pronto —<br/>renovação e troca de paciente]\n    E --> I[iframe valida o token do mount<br/>e emite app:ready { tokenValid }]\n    I --> J{tokenValid?}\n    J -->|true| K[prescrição pronta]\n    J -->|false| L[host reemite e entrega<br/>por update:token]\n    L --> I\n    K --> M[médico conclui →<br/>prescription:created { pubId }]\n    G -.->|qualquer momento| H[unmount&#40;&#41;<br/>port.close + iframe.remove<br/>+ rejeita Promises pendentes]\n\n    style A fill:#dbeafe,stroke:#3b82f6\n    style D fill:#fef3c7,stroke:#f59e0b\n    style K fill:#dcfce7,stroke:#22c55e\n    style M fill:#dcfce7,stroke:#22c55e\n    style H fill:#fee2e2,stroke:#ef4444\n```\n\nO `engine:mount` leva o `token` e o `initialValue`, então o caminho feliz não passa por command nenhum: o iframe hidrata, valida a credencial e emite `app:ready`. O host **deve sempre aguardar `app:init`** antes de enviar commands. Commands enviados antes de `render()` (ou após `unmount()`) **rejeitam imediatamente** com `CommandError` (`type: 'no_active_channel'`) e warn em dev — o engine não enfileira para evitar mascarar bugs de ordem. No `unmount()`, Promises ainda pendentes rejeitam com `CommandError` (`type: 'channel_closed'`).\n\nDepois de validar o token — o da montagem, e o de cada `update:token` com valor novo —\no iframe emite **`app:ready`** com `{ tokenValid: boolean }`. Hosts podem observar esse\nevento para confirmar que a prescrição está pronta **e** que o token foi aceito; quando\n`tokenValid` for `false`, o iframe fica numa tela de espera por credencial (sem fazer\nnenhuma chamada ao BFF) e a recuperação é entregar outro token por `update:token`. O\nevento é **aditivo** — iframes que não o emitem continuam funcionando, com o canal\nconsiderado pronto já no `app:init`.\n\n**Sem token utilizável nada é requisitado.** Se a montagem chegar sem o campo `token`\n(ou com um valor que não é token), o host recebe `app:error` com `missing_token` /\n`invalid_token` e o iframe não faz uma única chamada ao BFF. Os dois são **diagnóstico,\nnão porta fechada**: o canal segue aberto e um `update:token` bom faz a tela seguir sem\nremontar.\n\n### `prescription:created` — prescrição criada\n\nQuando o médico conclui e o backend cria a prescrição, o iframe emite\n**`prescription:created`** com o `pubId` — o ID da prescrição pai, a chave pela qual\no seu sistema consulta a prescrição na API.\n\n```ts\nimport type { PrescriptionCreatedPayload } from '@afyadigital/receitapro-engine'\n\nengine.events.listen<PrescriptionCreatedPayload>('prescription:created', ({ payload }) => {\n  void consultarPrescricao(payload.pubId)\n})\n```\n\nO evento sai **assim que a criação responde — antes e independentemente da assinatura\ndigital**. Ele diz que a prescrição existe e já é consultável, não que foi assinada: o\nmédico pode seguir sem assinar, e a assinatura pode falhar depois sem que isso mude o\nque você já recebeu. É emitido uma única vez por prescrição e **não ecoa `id`** — é\nevento de ciclo de vida, não resposta a command.\n\n---\n\n## Commands — API Promise\n\nApós o handshake, toda comunicação com o iframe acontece pelo `MessageChannel` privado via `engine.commands`. Cada método retorna uma `Promise` — `await` direto:\n\n```ts\nengine.commands.update<T>(scope, payload)  // Promise<T> — \"update:<scope>\"\nengine.commands.add<T>(scope, payload)     // Promise<T> — \"add:<scope>\"\nengine.commands.search<T>(scope, payload?) // Promise<T> — \"search:<scope>\"\n```\n\n> Os três métodos existem porque o engine é **transporte puro** — ele não conhece scopes. Mas o canal só trata dois: `update:token` e `update:patient` (ver [Catálogo de commands](#catálogo-de-commands)). Qualquer outro `scope` trafega e volta recusado com `app:error` (`type: 'unsupported_command'`).\n\nCada chamada:\n\n1. Gera um `id` interno (UUID v4) — **detalhe de transporte**, não exposto.\n2. Embrulha o envelope `{ type, id, payload }` e posta no `port1`.\n3. Registra uma Promise pendente que **resolve** com o `payload` do evento de resposta (cujo `id` casa) ou **rejeita** com `CommandError` se o iframe emitir `app:error`.\n\nO engine **não valida** o payload — toda validação acontece no `@receitapro/web` (Zod). Para DX, hosts importam os tipos do próprio `@afyadigital/receitapro-engine` (`PatientInitial`, `InitialValue`, `AppErrorPayload`, `AppReadyPayload`) — re-exportados de `@rx/contracts` como dependência transitiva.\n\n### Uso com `await`\n\n```ts\nimport type { PatientInitial } from '@afyadigital/receitapro-engine'\nimport { CommandError } from '@afyadigital/receitapro-engine'\n\nconst paciente: PatientInitial = { id: 'pac-002', name: 'João Souza' }\n\ntry {\n  const { status } = await engine.commands.update<{\n    status: 'updated' | 'cleared'\n    message: string\n  }>('patient', paciente)\n  console.log('paciente aplicado:', status) // 'updated'\n} catch (err) {\n  if (err instanceof CommandError) {\n    // err.payload é o AppErrorPayload: { type, message, issues? }\n    console.error('falhou:', err.payload)\n  }\n}\n```\n\n### Paralelismo natural\n\n```ts\nawait Promise.all([\n  engine.commands.update('token', tokenRenovado),\n  engine.commands.update('patient', paciente),\n])\n```\n\n> Eventos de ciclo de vida (`app:init`, `app:ready`, `token:expired`, `prescription:created`) **não** correspondem a um command e continuam sendo entregues via `engine.events.listen(...)`. A Promise API substitui apenas a correlação `command ↔ resposta`.\n\n### Trocar o paciente depois da montagem — `update:patient`\n\n`initialValue.patient` cobre a montagem. Para hosts que trocam o paciente **sem recarregar o iframe** — o médico muda de ficha na sua tela e a prescrição precisa acompanhar — existe `commands.update('patient', ...)`.\n\n```ts\nimport type { PatientInitial } from '@afyadigital/receitapro-engine'\nimport { CommandError } from '@afyadigital/receitapro-engine'\n\nconst { status, message } = await engine.commands.update<{\n  status: 'updated' | 'cleared'\n  message: string\n}>('patient', {\n  id: 'pac-002', // id NO SEU sistema — mesma chave do `initialValue.patient`\n  name: 'João Souza',\n  cpf: '05180005329',\n})\n\nconsole.log(status, message) // → 'updated' 'Paciente alterado com sucesso'\n```\n\nO payload é o **mesmo `PatientInitial`** aceito em `initialValue.patient` — mesmos campos obrigatórios (`id`, `name`), mesmas regras de tudo-ou-nada em `address` e `lme`. Veja [`patient` — o que o host pode pré-preencher](#patient--o-que-o-host-pode-pré-preencher) para a lista completa.\n\n**É substituição, não merge.** O paciente enviado troca integralmente o anterior; campos omitidos não são herdados da montagem nem da troca anterior. Mande o cadastro completo a cada chamada.\n\n#### Limpar o paciente\n\n`payload: null` remove o paciente e devolve a seleção à tela embarcada:\n\n```ts\nawait engine.commands.update('patient', null) // → { status: 'cleared', ... }\n```\n\n#### O que a Promise resolve\n\n`update:patient` é o único command que resolve com conteúdo — os demais resolvem com `null`:\n\n| Campo     | Valor                                                                        |\n| --------- | ---------------------------------------------------------------------------- |\n| `status`  | `'updated'` (paciente aplicado) ou `'cleared'` (removido)                    |\n| `message` | Texto pronto para log/toast. Não ramifique por ele — ramifique por `status`. |\n\n#### O que a troca **não** mexe\n\n- **Os itens já rascunhados** (medicamentos, vacinas, exames) permanecem. É deliberado: só o host sabe se a troca é correção de cadastro ou mudança de atendimento. Quando for mudança de verdade, cabe ao host limpar a prescrição.\n- **O `appointment`** — que, se existir, bloqueia o command por completo (abaixo).\n\n#### Recusas\n\n| `payload.type`    | Quando                                                        | Estado após a recusa                           |\n| ----------------- | ------------------------------------------------------------- | ---------------------------------------------- |\n| `invalid_patient` | Payload reprovado no schema do produto (ver `payload.issues`) | O paciente anterior é preservado integralmente |\n\n```ts\ntry {\n  await engine.commands.update('patient', novoPaciente)\n} catch (err) {\n  if (err instanceof CommandError) {\n    const { type, issues } = err.payload as { type: string; issues?: unknown[] }\n    if (type === 'invalid_patient') console.error('cadastro rejeitado:', issues)\n  }\n}\n```\n\n> **O modo gerenciado não bloqueia este command.** A trava vale para a UI do médico; o host, dono da escolha do paciente, troca ou corrige quando quiser — inclusive com um atendimento na montagem. Limpar com `payload: null` devolve a seleção ao médico. Note que _editar_ o cadastro pela UI também continua liberado: o que o host gerencia é a _escolha_ do paciente, não o cadastro.\n\n### Sessão — formato do token e renovação\n\nO token de prescrição é a credencial de **todas** as chamadas que o produto embarcado faz ao BFF. O host o obtém no seu backend (ver [guia de integração](./integration-docs/partner-integration-guide.md)) e o entrega pelo canal.\n\n**Formato: o JWT cru.** O produto embarcado monta o header `Authorization: Bearer <token>` por conta própria — mandar o valor já prefixado produziria `Bearer Bearer eyJ…` e 401 em toda chamada.\n\n```ts\nawait engine.commands.update('token', access_token) // ✅ o JWT cru\nawait engine.commands.update('token', `Bearer ${access_token}`) // ⚠️ prefixo tolerado, não use\n```\n\n> Versões do guia até a 0.3.1 pediam o valor prefixado. O prefixo continua sendo aceito — e removido — para não quebrar quem integrou seguindo aquela versão, mas o contrato é o JWT cru.\n\n**O token viaja no handshake.** `new RxEngine({ token })` é a entrega: o valor vai no `engine:mount` e o iframe monta autenticado, sem depender de nenhum command. `commands.update('token', …)` tem dois papéis, os dois posteriores à montagem:\n\n- **renovação** em sessão — o token vence e o host entrega o próximo (abaixo);\n- **correção** — a montagem trouxe um token que o BFF recusou (`app:ready` com `tokenValid: false`) e o host entrega outro. O host não tem como saber se o token presta antes de entregar: só a validação contra o BFF diz. Uma tentativa de correção é caso normal de operação; recusada de novo, é credencial errada.\n\nAté a 0.5.0 este command era a entrega primária. Hosts que continuarem disparando-o no `app:init` seguem funcionando — é o mesmo valor entregue duas vezes, e o segundo não reemite `app:ready`.\n\n**Sem refresh token: renovar é reemitir** — repetir a chamada que gerou o token. Use o `expires_in` da resposta do BFF para renovar **antes** do vencimento; é o caminho preferido, porque nenhuma chamada do médico precisa falhar primeiro. O `expires_in` é configuração de servidor: leia o valor da resposta em vez de fixar um número no código.\n\n```ts\n// Caminho preferido: agende pela validade informada pelo BFF, com margem.\nconst { access_token, expires_in } = await emitirTokenDePrescricao()\nsetTimeout(renovar, (expires_in - 60) * 1000)\n\n// Rede de segurança: o produto avisa quando a credencial for recusada.\nengine.events.listen('token:expired', renovar)\n```\n\nReentregar por `update:token` **retoma a sessão no lugar onde ela parou** — a prescrição em rascunho é preservada e o iframe não é remontado.\n\n### Catálogo de commands\n\nO canal tem exatamente dois commands: **sessão** e **contexto do paciente**. Nenhum dos dois é necessário no caminho feliz — a credencial e o contexto inicial vão na montagem.\n\n| Método                            | `type` no envelope | Payload                               | Resolve com           |\n| --------------------------------- | ------------------ | ------------------------------------- | --------------------- |\n| `commands.update('token', ...)`   | `update:token`     | `string` — o **JWT cru**, sem prefixo | `null`                |\n| `commands.update('patient', ...)` | `update:patient`   | `PatientInitial` ou `null`            | `{ status, message }` |\n\n> **`update:token` não é a entrega inicial da credencial.** O token da montagem vai no `RxEngineConfig` e viaja no `engine:mount`; o command cobre a **renovação** em sessão e a **correção** do token que a validação recusou. Ver [Sessão — formato do token e renovação](#sessão--formato-do-token-e-renovação).\n\n> **Qualquer outro `scope` é recusado.** O iframe responde `app:error` com `type: 'unsupported_command'` e a `Promise` rejeita com `CommandError`. Compor a prescrição a partir do sistema do parceiro (adicionar medicamento, vacina, exame, buscar no catálogo) **não** passa pelo canal: o que o host tem para isso é o `initialValue` na montagem.\n\n> **`prescription:created` avisa, mas só enquanto o iframe está aberto.** O canal emite o evento assim que o backend cria a prescrição (ver [`prescription:created` — prescrição criada](#prescriptioncreated--prescrição-criada)), mas se o médico fechar a aba antes disso o host não recebe nada — não há reentrega. Para saber da receita **de forma durável**, independente da aba estar aberta, o lugar continua sendo um webhook do backend. **Esse webhook ainda não existe:** se o seu fluxo depende de uma notificação durável, fale com o time de Prescrição antes de planejar a integração. O evento do canal não substitui o webhook.\n\n> **Envelope obrigatório.** O produto embarcado só processa mensagens com `type` **e** `id`. O engine sempre gera o `id`, então isso só afeta quem tenta falar com o canal por fora da API pública — mensagens sem `id` são descartadas em silêncio e nenhuma resposta volta.\n\n### Eventos de retorno\n\nA coluna `id?` indica se o evento ecoa o `id` interno do envelope — quando sim, **resolve ou rejeita a `Promise` correspondente**; quando não, é entregue apenas via `engine.events.listen(...)`.\n\n| Evento                 | Quando                                                                               | `id`?                     | `payload`                                             |\n| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------- | ----------------------------------------------------- |\n| `app:init`             | Handshake concluído: `initialValue` processado, canal pronto para commands           | não                       | —                                                     |\n| `app:ready`            | Quando a validação do token conclui — o da montagem, e o de cada `update:token` novo | não                       | `{ tokenValid: boolean }`                             |\n| `token:expired`        | Um token que já fora aceito passou a ser recusado pelo BFF (401/403)                 | não                       | —                                                     |\n| `prescription:created` | O backend criou uma prescrição — antes da assinatura digital                         | não                       | `{ pubId: string }`                                   |\n| `app:error`            | `token` ou `initialValue` recusado na montagem, ou command recusado                  | montagem não; command sim | `AppErrorPayload`                                     |\n| `update:token`         | Ack do command homônimo                                                              | sim                       | `null`                                                |\n| `update:patient`       | Sucesso do command homônimo                                                          | sim                       | `{ status: 'updated' \\| 'cleared', message: string }` |\n\n**Os eventos de resposta ecoam o próprio `type` do command** (`update:token` → `update:token`), e não uma forma `<escopo>:<verbo>`. O único evento de domínio emitido é `prescription:created`; nomes como `medication:added`, `token:updated` e `prescription:saved` **não são emitidos** — não escreva listeners contando com eles.\n\n> **`app:ready` é reemitido a cada token novo.** O primeiro sai da validação do token da montagem; depois, cada `update:token` com um valor diferente do anterior faz o produto revalidar e emitir de novo. O mesmo token entregue duas vezes não reemite.\n\n> **`tokenValid: false` e `token:expired` não se sobrepõem.** Uma recusa já na primeira validação é `app:ready` com `tokenValid: false` — o token **nunca** foi aceito. Um token que **foi aceito** e depois deixou de valer chega como `token:expired`. Nos dois casos a ação imediata é a mesma — gerar um token novo e chamar `commands.update('token', novoToken)`, sem remontar o iframe —, mas o que elas dizem sobre a emissão é oposto: `token:expired` é rotina, e `tokenValid: false` repetido é a sua emissão que está errada. Renovar de forma proativa pelo `expires_in` do BFF continua sendo o caminho preferido.\n\n> **A tela de espera é diferente nos dois casos.** Um token que expirou põe um aviso de reconexão **por cima** da prescrição, que continua montada: rascunho, formulário em edição e diálogos abertos sobrevivem. Um token que nunca foi aceito não tem prescrição atrás para preservar, e a tela diz que está aguardando a liberação do acesso. Em nenhum dos dois há login dentro do iframe — a saída é sempre o host reentregar o token.\n\n### Erros e casos de borda\n\n| Situação                                             | Como o host fica sabendo                                                                                                                              | Estado resultante                                                                                                                                   |\n| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `initialValue` ausente, `null` ou não-objeto         | Nenhum sinal — não é erro                                                                                                                             | Prescrição em branco, seleção de paciente normal                                                                                                    |\n| `initialValue` reprovado na validação                | `app:error` com `type: 'invalid_initial_value'` e `issues`                                                                                            | Nada hidrata; o `app:init` sai do mesmo jeito e a UI segue operável                                                                                 |\n| Montagem sem o campo `token`                         | `app:error` com `type: 'missing_token'`                                                                                                               | Nenhuma chamada ao BFF; tela de espera por credencial. Recupera com `update:token`                                                                  |\n| Montagem com `token` que não é token utilizável      | `app:error` com `type: 'invalid_token'`                                                                                                               | Nada é gravado; idem acima — recupera com `update:token`                                                                                            |\n| `appointment` sem `patient`                          | Erro de compilação em TS; `Error` no construtor; `render()` no-op + `app:error` (`appointment_without_patient`); `console.error` em qualquer ambiente | Nenhum iframe é criado                                                                                                                              |\n| Command antes de `render()` ou depois de `unmount()` | `CommandError` com `payload.type: 'no_active_channel'` (+ `console.warn` fora de produção)                                                            | Nada trafega pelo canal                                                                                                                             |\n| `update:patient` com payload inválido                | `CommandError` com `type: 'invalid_patient'` e `issues`                                                                                               | O paciente anterior é preservado integralmente                                                                                                      |\n| Payload não clonável (`structuredClone`)             | `CommandError` com `type: 'post_failed'` e a `cause` original                                                                                         | Nada foi postado                                                                                                                                    |\n| `app:ready` com `tokenValid: false`                  | Evento de ciclo de vida                                                                                                                               | O token nunca foi aceito. Tela de espera por credencial; uma tentativa de correção por `update:token` é legítima, recusa repetida é erro de emissão |\n| `app:init` nunca chega                               | Silêncio — o engine não tem timeout próprio                                                                                                           | **Implemente um timeout no host** (referência: 20s) e ofereça retry                                                                                 |\n| `app:ready` nunca chega                              | Silêncio                                                                                                                                              | Mesmo timeout. O evento é aditivo: um iframe em versão antiga pode não emiti-lo — considere o canal pronto no `app:init`                            |\n| `unmount()` com commands em voo                      | Cada Promise pendente rejeita com `CommandError` (`type: 'channel_closed'`)                                                                           | Canal fechado, iframe removido, listeners limpos                                                                                                    |\n\n> Os `type` de transporte (`no_active_channel`, `post_failed`, `channel_closed`) **não** cruzam o `MessageChannel` — são gerados pelo próprio engine e só aparecem em `CommandError.payload`. Os de contrato (`invalid_initial_value`, `appointment_without_patient`, `missing_token`, `invalid_token`, `invalid_patient`, `unsupported_command` — mais o obsoleto `patient_change_not_allowed`) fazem parte de `AppErrorType`, exportado pelo pacote: importe o tipo em vez de comparar strings soltas.\n\n---\n\n## Migração do widget legado\n\nHosts que vinham do widget anterior precisam dos ajustes abaixo — os três primeiros são de contrato e quebram a integração se ficarem de fora:\n\n| Antes (widget legado)                  | Agora (`@afyadigital/receitapro-engine`)                                                                                                                     |\n| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `apointment` (um `p`)                  | **`appointment`**, com dois `p`, na raiz do `initialValue` — e **exige `patient` junto**                                                                     |\n| `address` como string única            | **`address` como objeto** (`PatientAddress`), bloco tudo-ou-nada. Não há parsing de string do lado da prescrição: quebre a string nos campos antes de enviar |\n| `age`                                  | **Removido do contrato.** Envie `birthdate` em ISO (`YYYY-MM-DD`)                                                                                            |\n| Trocar de paciente remontando o iframe | `commands.update('patient', …)` — a qualquer momento, com ou sem `appointment`                                                                               |\n| Token com prefixo `Bearer`             | `access_token` **cru**, tanto no `RxEngineConfig.token` quanto no `commands.update('token', …)`                                                              |\n\nAlém disso, um comportamento novo que muda a operação: **enviar `patient` trava a seleção na UI** (modo gerenciado), com ou sem `appointment`. Hosts que mandavam paciente só para pré-preencher e contavam com o médico podendo trocar devem **omitir** o `patient` e deixar a seleção com o médico.\n\n> **A edição do cadastro feita dentro da prescrição não volta pelo canal.** Não há evento de \"paciente editado\". A alteração é gravada no prescription-bff pela chave `external_id` — o mesmo `patient.id` que você enviou. Hosts que precisem refletir a edição no próprio cadastro devem observar o prescription-bff, não o `RxEngine`.\n\n---\n\n## API\n\n### `class RxEngine`\n\n#### `constructor(config: RxEngineConfig)`\n\n| Campo           | Tipo                                         | Obrigatório | Descrição                                                                                                                                                                      |\n| --------------- | -------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `container`     | `HTMLElement`                                | ✓           | Elemento DOM onde o iframe será inserido (`appendChild`).                                                                                                                      |\n| `token`         | `string`                                     | ✓           | Credencial da sessão — o **JWT cru**, sem `Bearer`. Vai no `engine:mount`.                                                                                                     |\n| `initialValue?` | `InitialValue`                               | —           | Estado inicial opcional para hidratar as stores antes do primeiro render.                                                                                                      |\n| `env?`          | `'production' \\| 'staging' \\| 'development'` | —           | Override explícito de ambiente. Default: `process.env.RX_ENV` → `production`.                                                                                                  |\n| `devqaUrl?`     | `string`                                     | —           | URL `http`/`https` do web a embutir quando o ambiente resolvido é `development`. Ignorada em `staging`/`production` e quando não é URL absoluta — o motivo vai para o console. |\n\nLança `Error` se `container` ou `token` estiverem ausentes, ou se `initialValue` trouxer `appointment` sem `patient`.\n\n#### `render(): void`\n\nCria o iframe, instala o `MessageChannel` e registra o listener de `load` que disparará o handshake `engine:mount`. **Idempotente** — chamadas subsequentes são no-op.\n\nTambém vira no-op quando o `initialValue` traz `appointment` sem `patient` (caso em que o config foi mutado depois da construção): nenhum iframe é inserido e o host recebe `app:error` com `type: 'appointment_without_patient'`. Não lança — `render()` costuma ser chamado fora de `try/catch`, então o sinal é o evento.\n\n#### `unmount(): void`\n\nRemove o iframe do DOM, fecha o `port1`, remove o listener de `load` e limpa todos os event handlers. Idempotente e seguro para chamar antes de `render()`.\n\n#### `events: EngineEventEmitter`\n\nDispatcher de eventos recebidos pelo `port1`. Use `engine.events.listen(eventName, handler)` (ver abaixo).\n\n#### `commands: Commands`\n\nAPI de envio de commands ao iframe. Cada método retorna uma `Promise<T>` que resolve com o `payload` do evento de resposta ou rejeita com `CommandError`:\n\n| Método                                | Retorno      |\n| ------------------------------------- | ------------ |\n| `commands.add<T>(scope, payload)`     | `Promise<T>` |\n| `commands.update<T>(scope, payload)`  | `Promise<T>` |\n| `commands.search<T>(scope, payload?)` | `Promise<T>` |\n\nAntes de `render()` (ou após `unmount()`) chamadas rejeitam imediatamente com `CommandError` (`type: 'no_active_channel'`) e warn em dev. No `unmount()`, Promises pendentes rejeitam com `CommandError` (`type: 'channel_closed'`).\n\n`commands.update('patient', …)` troca o paciente depois da montagem (payload `PatientInitial`) ou o remove (`payload: null`), e é o único command que resolve com conteúdo: `{ status: 'updated' | 'cleared', message: string }`. Ver [`update:patient`](#trocar-o-paciente-depois-da-montagem--updatepatient).\n\nVale a qualquer momento depois da montagem, com ou sem `appointment`: o modo gerenciado trava a UI do médico, não o canal do host.\n\n`commands.update('token', …)` entrega a credencial da sessão — o **JWT cru**. Ver [Sessão — formato do token e renovação](#sessão--formato-do-token-e-renovação).\n\n#### `CommandError extends Error`\n\nLançado pela rejeição das Promises de `commands.*`. Carrega:\n\n| Campo     | Tipo      | Conteúdo                                                                                         |\n| --------- | --------- | ------------------------------------------------------------------------------------------------ |\n| `message` | `string`  | Resumo textual — extraído de `payload.message`/`payload.type` ou um default genérico.            |\n| `payload` | `unknown` | Payload original do `app:error` — ver `AppErrorPayload` abaixo — ou um marcador local do engine. |\n\n#### `AppErrorPayload`\n\nPayload de todo `app:error`, tanto o evento de ciclo de vida quanto a resposta a um command que vira `CommandError`:\n\n| Campo     | Tipo                                | Conteúdo                                                                             |\n| --------- | ----------------------------------- | ------------------------------------------------------------------------------------ |\n| `type`    | `AppErrorType`                      | Motivo do erro — **é por aqui que o host ramifica**.                                 |\n| `message` | `string`                            | Descrição técnica para log/diagnóstico. **Não é copy de UI** e pode mudar sem aviso. |\n| `issues`  | `readonly unknown[]` \\| `undefined` | Detalhes por campo de uma falha de validação. Opaco por design: serve para log.      |\n\n`AppErrorType` é a fonte única de verdade dos motivos — importe o tipo em vez de comparar strings soltas:\n\n| `type`                        | Quando                                                                                        | Efeito                                                     |\n| ----------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |\n| `invalid_initial_value`       | O envelope de `initialValue` não passou na validação da montagem                              | Nada hidrata — **não há recuperação sem remontar**         |\n| `appointment_without_patient` | `initialValue.appointment` sem `patient`                                                      | `render()` vira no-op, nenhum iframe é criado              |\n| `invalid_patient`             | Payload de `commands.update('patient', …)` inválido                                           | O paciente anterior é preservado                           |\n| `patient_change_not_allowed`  | _Obsoleto_ — nunca mais emitido (ver nota abaixo)                                             | —                                                          |\n| `missing_token`               | A montagem chegou sem o campo `token`                                                         | Nenhuma chamada ao BFF; recupera por `update:token`        |\n| `invalid_token`               | O `token` da montagem, ou o payload de `commands.update('token', …)`, não é um JWT utilizável | O token anterior é preservado; na montagem, nada é gravado |\n| `unsupported_command`         | `scope` fora do contrato do canal (ver Catálogo de commands)                                  | Nada muda                                                  |\n\nOs dois primeiros invalidam a **montagem**: um `app:init` pode chegar depois deles (a UI embarcada segue operável do lado dela), então o host que precisa do contexto travado deve tratá-los como terminais e **não** voltar ao estado \"pronto\".\n\n`missing_token` e `invalid_token` também acontecem na montagem, mas **não** são terminais: o host não tem como validar o token antes de entregá-lo, então o conserto previsto é entregar outro por `commands.update('token', …)` — sem remontar. Enquanto isso o iframe não faz uma única chamada ao BFF.\n\n`patient_change_not_allowed` **não é mais emitido**. Até a 0.7.0 ele recusava `update:patient` enquanto houvesse atendimento na montagem; hoje o command vale sempre. O literal segue em `AppErrorType` para não quebrar o `switch` exaustivo de quem já o trata — e hosts presos a um engine anterior continuam recebendo a recusa local daquela versão.\n\nFalhas de **transporte** não usam `AppErrorType` — nunca cruzam o `MessageChannel` e só aparecem em `CommandError.payload`: `no_active_channel`, `post_failed` e `channel_closed`.\n\n#### `listen(event, handler): Unsubscribe`\n\nAtalho equivalente a `engine.events.listen(event, handler)` — útil para encadear diretamente na instância do engine.\n\n---\n\n### `class EngineEventEmitter`\n\n| Método                                 | Descrição                                                                           |\n| -------------------------------------- | ----------------------------------------------------------------------------------- |\n| `listen(event, handler) → Unsubscribe` | Registra um handler. Retorna função de unsubscribe.                                 |\n| `dispatch(message)`                    | Despacha um payload recebido pelo `port1`. Ignora payloads sem `{ event: string }`. |\n| `clear()`                              | Remove todos os handlers (chamado pelo `RxEngine.unmount()`).                       |\n\n**Isolamento de erros:** se um handler lançar, o erro é logado via `console.error` e os demais handlers continuam executando.\n\n---\n\n### Tipos públicos (re-exportados de `@rx/contracts`)\n\n| Tipo                 | Uso                                                            |\n| -------------------- | -------------------------------------------------------------- |\n| `InitialValue`       | Hidratação inicial (`new RxEngine({ initialValue })`)          |\n| `PatientInitial`     | `initialValue.patient`                                         |\n| `PatientAddress`     | `initialValue.patient.address`                                 |\n| `PatientLme`         | `initialValue.patient.lme`                                     |\n| `SkinColor`          | `initialValue.patient.lme.skinColor`                           |\n| `LmeResponsible`     | `initialValue.patient.lme.filledBy.responsible`                |\n| `AppointmentInitial` | `initialValue.appointment`                                     |\n| `AppErrorPayload`    | `payload` de `app:error` e de `CommandError`                   |\n| `AppErrorType`       | Motivos de `app:error` — ramifique por ele                     |\n| `AppReadyPayload`    | `payload` de `app:ready` (`{ tokenValid }`)                    |\n| `HostEvent`          | Nomes de eventos de ciclo de vida web→host (`token:expired`)   |\n| `MedicationPayload`  | `initialValue.medications[]` — **reservado**, ainda sem efeito |\n| `VaccinePayload`     | `initialValue.vaccines[]` — **reservado**, ainda sem efeito    |\n| `ExamPayload`        | `initialValue.exams[]` — **reservado**, ainda sem efeito       |\n\n```ts\nimport {\n  type AppReadyPayload,\n  type InitialValue,\n  type PatientInitial,\n} from '@afyadigital/receitapro-engine'\n```\n\n> Re-exports são apenas tipos — apagados no build, sem custo de runtime. O host não precisa instalar `@rx/contracts` diretamente.\n\n---\n\n### `resolveIframeUrl(env?: RxEnv, devqaUrl?: string): string`\n\nResolve a URL do iframe a partir de um override explícito ou de `process.env.RX_ENV`.\n\n```ts\nimport { IFRAME_URLS, resolveIframeUrl } from '@afyadigital/receitapro-engine'\n\nresolveIframeUrl('staging') // → URL do ambiente de staging\nresolveIframeUrl() // → resolve por `process.env.RX_ENV`, default `production`\nresolveIframeUrl('development', 'https://web-da-stack.example') // → a URL passada\nresolveIframeUrl('production', 'https://web-da-stack.example') // → produção; devqaUrl é ignorada\nresolveIframeUrl('development', 'web-da-stack.example') // → host local; devqaUrl sem esquema é recusada\nIFRAME_URLS.development // → host local, para desenvolvimento do próprio produto\n```\n\n> A `devqaUrl` vira o `src` do iframe **e** o `targetOrigin` do `postMessage`,\n> então só é aceita como URL `http`/`https` absoluta. Todo descarte — ambiente\n> errado ou valor inválido — explica o motivo em `console.error`.\n\n---\n\n## Exemplo completo — host React\n\n```tsx\nimport type { AppReadyPayload } from '@afyadigital/receitapro-engine'\nimport { RxEngine } from '@afyadigital/receitapro-engine'\nimport { useEffect, useRef } from 'react'\n\nexport function PrescricaoEmbed({ token, patient }: Props) {\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    if (!containerRef.current) return\n\n    const engine = new RxEngine({\n      container: containerRef.current,\n      token,\n      initialValue: { patient },\n    })\n\n    // `tokenValid: false` é o token que nunca foi aceito. Uma tentativa de correção\n    // cobre o caso legítimo (o token venceu entre a emissão e a montagem); da segunda\n    // em diante é erro de emissão, e insistir vira laço de emissão e recusa.\n    let correcaoTentada = false\n\n    // Listeners antes do `render()` — depois dele o `app:init` já pode ter passado.\n    const offReady = engine.events.listen<AppReadyPayload>('app:ready', async (e) => {\n      if (e.payload?.tokenValid !== false) return\n      if (correcaoTentada) return // revise a emissão em vez de reemitir\n      correcaoTentada = true\n      const novoToken = await emitirTokenDePrescricao() // seu backend\n      await engine.commands.update('token', novoToken)\n    })\n\n    // Rede de segurança da renovação: o token foi aceito e deixou de valer.\n    // Reemita pelo seu backend e reentregue — sem remontar o iframe.\n    const offExpired = engine.events.listen('token:expired', async () => {\n      const novoToken = await emitirTokenDePrescricao() // seu backend\n      await engine.commands.update('token', novoToken)\n    })\n\n    engine.render()\n\n    return () => {\n      offReady()\n      offExpired()\n      engine.unmount()\n    }\n  }, [token, patient])\n\n  return <div ref={containerRef} style={{ width: '100%', height: '100vh' }} />\n}\n```\n\n---\n\n## Segurança\n\n- O engine usa um `MessageChannel` privado — após o handshake, **toda** a comunicação acontece pelo par `port1` / `port2`, fora do alcance de outras `window`s no host. Mensagens que trafegam por um `MessagePort` não carregam origin (`MessageEvent.origin === ''`): o canal é ponto-a-ponto por construção, sem terceiros para interceptar ou injetar.\n- O único ponto sensível a origin é a transferência do `port2`, protegida pelo `targetOrigin` do `postMessage`, que é a **URL exata do iframe** (não `*`) — o browser só entrega a mensagem, e o port junto, se a `window` do iframe estiver naquela origin.\n- O `token` viaja nessa mesma transferência, e por isso está sob a mesma proteção: o `engine:mount` é postado com o `targetOrigin` exato do iframe, então o browser não entrega a mensagem — nem a credencial — se a `window` de destino não estiver naquela origin. Ele **não** trafega por `window.postMessage` com `'*'` em nenhum momento, e depois do handshake nunca mais sai da `window`.\n- **Não há allowlist de origin do integrador** (não existe um `allowedOrigin` na config). Ela seria redundante: o `targetOrigin` acima já pinça o destino da transferência, e a origin do _parent_ não participa de nenhuma garantia do canal.\n- A defesa contra clickjacking vive **dentro** do iframe (`@receitapro/web`), pois o produto é `embed-from-any-origin` (modelo SaaS widget).\n\n---\n\n## Resolução de ambiente\n\n| Estratégia                                            | Resultado                                         |\n| ----------------------------------------------------- | ------------------------------------------------- |\n| `new RxEngine({ ..., env: 'staging' })`               | Força `staging`.                                  |\n| `process.env.RX_ENV='development'` (build do host)    | Resolve para `development` (host local).          |\n| `new RxEngine({ ..., env: 'development', devqaUrl })` | Resolve para a `devqaUrl` no lugar do host local. |\n| Nada definido / valor inválido                        | Resolve para `production`.                        |\n\nVariáveis de ambiente são lidas em build time pelos bundlers usuais (Vite, Webpack, Next.js).\n\n---\n\n## Scripts\n\n```bash\npnpm --filter @afyadigital/receitapro-engine build       # vite (esm + .d.ts via vite-plugin-dts)\npnpm --filter @afyadigital/receitapro-engine dev         # vite build --watch\npnpm --filter @afyadigital/receitapro-engine test        # vitest run\npnpm --filter @afyadigital/receitapro-engine test:watch  # vitest\npnpm --filter @afyadigital/receitapro-engine lint        # eslint\npnpm --filter @afyadigital/receitapro-engine type-check  # tsc --noEmit\n```\n\n---\n\n## Exemplo de diagrama\n\n> O npm não renderiza diagramas `mermaid` nativamente — por isso este é servido como **imagem** via [mermaid.ink](https://mermaid.ink), que renderiza tanto no npm quanto no GitHub.\n\n![Exemplo de diagrama mermaid (graph TD)](https://mermaid.ink/img/Z3JhcGggVEQ7CiAgICBBLS0-QjsKICAgIEEtLT5DOwogICAgQi0tPkQ7CiAgICBDLS0-RDs)\n\n---\n\n## Publicação (Changesets)\n\nEste é o **único** pacote do monorepo publicado no npm — como **`@afyadigital/receitapro-engine`**, no registro público. O versionamento é dirigido por [Changesets](https://github.com/changesets/changesets) a partir da raiz do monorepo (config em `.changeset/config.json`: `access: public`, `baseBranch: main`), e a publicação roda por um **workflow manual** do GitHub Actions — não pelo bot de release do Changesets — para casar com os workflows `deploy-*` existentes.\n\n### 1. Durante o desenvolvimento — registre um changeset\n\nAo alterar o engine, descreva a mudança (`patch` / `minor` / `major`) e commite o `.changeset/*.md` gerado junto com o PR:\n\n```bash\npnpm changeset\n```\n\nOs changesets se acumulam na `main` até alguém cortar um release.\n\n### 2. Para publicar — dispare o workflow de release\n\nUm mantenedor dispara o workflow **Release engine to npm** (GitHub → Actions → _Run workflow_, definido em `.github/workflows/release-engine-npm.yml`). O job:\n\n1. **Resolve a versão.** Se há changesets pendentes, aplica-os com `changeset version` (atualiza o `version` no `package.json` do engine — **não edite à mão** — e escreve o `CHANGELOG.md`). Se **não** há changeset pendente, o input `bump` do workflow (`patch` por padrão) gera um para o engine, permitindo cortar o release mesmo assim. Havendo changeset commitado, o input é ignorado (evita bump duplo).\n2. **Commita** o bump como `chore: release packages`.\n3. **Builda só o engine** — `turbo run build --filter=@afyadigital/receitapro-engine`.\n4. **Publica** com `pnpm changeset publish` — sobe os pacotes não-privados (hoje, só o engine) e cria a tag `@afyadigital/receitapro-engine@<versão>`.\n5. **Faz push** do commit de versão e das tags de volta para a `main`.\n\n### Pré-requisitos (uma vez)\n\n- Secret **`NPM_TOKEN`** — token de automação com permissão de publish no escopo `@afyadigital`. A auth do npm é escrita no `.npmrc` **só no runner** (nunca commitada).\n- Permissão de **escrita** para o workflow (`contents: ","readmeFilename":"README.md"}