@pyric/cli 0.1.0-alpha.30 → 0.1.0-alpha.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/assurance/.generated/conformance-verdicts.d.ts +8 -0
  2. package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
  3. package/dist/assurance/.generated/conformance-verdicts.js +8 -0
  4. package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
  5. package/dist/bridge/server/peer.d.ts +10 -0
  6. package/dist/bridge/server/peer.d.ts.map +1 -1
  7. package/dist/bridge/server/peer.js +46 -1
  8. package/dist/bridge/server/peer.js.map +1 -1
  9. package/dist/cli/serve.d.ts +2 -0
  10. package/dist/cli/serve.d.ts.map +1 -1
  11. package/dist/cli/serve.js +13 -2
  12. package/dist/cli/serve.js.map +1 -1
  13. package/dist/conformance/.generated/can-i-use-browser.js +2 -2
  14. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  15. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  16. package/dist/conformance/.generated/can-i-use.js +1 -1
  17. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  18. package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
  19. package/dist/conformance/.generated/conformance-docs.js +1 -1
  20. package/dist/conformance/.generated/conformance-docs.js.map +1 -1
  21. package/dist/register/app-bridge-admin-app-check.d.ts +3 -0
  22. package/dist/register/app-bridge-admin-app-check.d.ts.map +1 -0
  23. package/dist/register/app-bridge-admin-app-check.js +43 -0
  24. package/dist/register/app-bridge-admin-app-check.js.map +1 -0
  25. package/dist/register/app-bridge-app-check.d.ts +4 -0
  26. package/dist/register/app-bridge-app-check.d.ts.map +1 -1
  27. package/dist/register/app-bridge-app-check.js +4 -0
  28. package/dist/register/app-bridge-app-check.js.map +1 -1
  29. package/dist/register/app-bridge-shadow.d.ts +4 -2
  30. package/dist/register/app-bridge-shadow.d.ts.map +1 -1
  31. package/dist/register/app-bridge-shadow.js +29 -5
  32. package/dist/register/app-bridge-shadow.js.map +1 -1
  33. package/dist/register/index.d.ts.map +1 -1
  34. package/dist/register/index.js +21 -0
  35. package/dist/register/index.js.map +1 -1
  36. package/dist/serve/ai-diagnostics-relay.d.ts +4 -1
  37. package/dist/serve/ai-diagnostics-relay.d.ts.map +1 -1
  38. package/dist/serve/ai-diagnostics-relay.js +3 -0
  39. package/dist/serve/ai-diagnostics-relay.js.map +1 -1
  40. package/dist/serve/ai-proxy.d.ts +21 -1
  41. package/dist/serve/ai-proxy.d.ts.map +1 -1
  42. package/dist/serve/ai-proxy.js +39 -14
  43. package/dist/serve/ai-proxy.js.map +1 -1
  44. package/dist/serve/ai-terminal-blocks.d.ts +6 -4
  45. package/dist/serve/ai-terminal-blocks.d.ts.map +1 -1
  46. package/dist/serve/ai-terminal-blocks.js +7 -4
  47. package/dist/serve/ai-terminal-blocks.js.map +1 -1
  48. package/dist/serve/ai-terminal-text.d.ts +8 -0
  49. package/dist/serve/ai-terminal-text.d.ts.map +1 -1
  50. package/dist/serve/ai-terminal-text.js +10 -0
  51. package/dist/serve/ai-terminal-text.js.map +1 -1
  52. package/dist/serve/bundler.d.ts +40 -0
  53. package/dist/serve/bundler.d.ts.map +1 -1
  54. package/dist/serve/bundler.js +133 -2
  55. package/dist/serve/bundler.js.map +1 -1
  56. package/dist/serve/entries/ai.d.ts.map +1 -1
  57. package/dist/serve/entries/ai.js +28 -26
  58. package/dist/serve/entries/ai.js.map +1 -1
  59. package/dist/serve/entries/app-ai-passthrough.d.ts +4 -4
  60. package/dist/serve/entries/app-ai-passthrough.d.ts.map +1 -1
  61. package/dist/serve/entries/app-ai-passthrough.js +8 -52
  62. package/dist/serve/entries/app-ai-passthrough.js.map +1 -1
  63. package/dist/serve/firebase-module-swap.d.ts +22 -0
  64. package/dist/serve/firebase-module-swap.d.ts.map +1 -1
  65. package/dist/serve/firebase-module-swap.js +51 -3
  66. package/dist/serve/firebase-module-swap.js.map +1 -1
  67. package/dist/serve/gemini-error-redaction.d.ts +6 -0
  68. package/dist/serve/gemini-error-redaction.d.ts.map +1 -0
  69. package/dist/serve/gemini-error-redaction.js +61 -0
  70. package/dist/serve/gemini-error-redaction.js.map +1 -0
  71. package/dist/serve/gemini-proxy.d.ts +40 -0
  72. package/dist/serve/gemini-proxy.d.ts.map +1 -0
  73. package/dist/serve/gemini-proxy.js +105 -0
  74. package/dist/serve/gemini-proxy.js.map +1 -0
  75. package/dist/serve/hosted/method-protocol.d.ts +4 -4
  76. package/dist/serve/html-injection.d.ts +2 -0
  77. package/dist/serve/html-injection.d.ts.map +1 -1
  78. package/dist/serve/html-injection.js +4 -2
  79. package/dist/serve/html-injection.js.map +1 -1
  80. package/dist/serve/namespace.d.ts +9 -0
  81. package/dist/serve/namespace.d.ts.map +1 -1
  82. package/dist/serve/namespace.js +71 -15
  83. package/dist/serve/namespace.js.map +1 -1
  84. package/dist/serve/sandbox-session.d.ts +3 -0
  85. package/dist/serve/sandbox-session.d.ts.map +1 -1
  86. package/dist/serve/sandbox-session.js +1 -0
  87. package/dist/serve/sandbox-session.js.map +1 -1
  88. package/dist/serve/site-ui/docs/ai-compat/index.html +29 -10
  89. package/dist/serve/site-ui/docs/ai-compat.md +30 -9
  90. package/dist/serve/site-ui/docs/app-check-compat/index.html +42 -18
  91. package/dist/serve/site-ui/docs/app-check-compat.md +42 -18
  92. package/dist/serve/site-ui/docs/auth-compat/index.html +4 -4
  93. package/dist/serve/site-ui/docs/auth-compat.md +4 -4
  94. package/dist/serve/site-ui/docs/build/ai-logic/index.html +21 -5
  95. package/dist/serve/site-ui/docs/build/ai-logic.md +23 -2
  96. package/dist/serve/site-ui/docs/database-compat/index.html +4 -4
  97. package/dist/serve/site-ui/docs/database-compat.md +4 -4
  98. package/dist/serve/site-ui/docs/firestore-compat/index.html +4 -4
  99. package/dist/serve/site-ui/docs/firestore-compat.md +4 -4
  100. package/dist/serve/site-ui/docs/index.json +10 -0
  101. package/dist/serve/site-ui/docs/messaging-compat/index.html +4 -4
  102. package/dist/serve/site-ui/docs/messaging-compat.md +4 -4
  103. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +4 -0
  104. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api.md +5 -0
  105. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-app-check-reference-api/index.html +2 -0
  106. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-app-check-reference-api.md +3 -0
  107. package/dist/serve/site-ui/docs/storage-compat/index.html +5 -5
  108. package/dist/serve/site-ui/docs/storage-compat.md +5 -5
  109. package/dist/serve/site-ui/docs/tutorial/index.html +11 -1
  110. package/dist/serve/site-ui/docs/tutorial.md +12 -1
  111. package/dist/serve/site-ui/examples/chess/index.html +1 -1
  112. package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
  113. package/dist/serve/vite-sandbox-generation.js +2 -0
  114. package/dist/serve/vite-sandbox-generation.js.map +1 -1
  115. package/dist/serve/worker/ai-proxy-paths.d.ts +32 -0
  116. package/dist/serve/worker/ai-proxy-paths.d.ts.map +1 -0
  117. package/dist/serve/worker/ai-proxy-paths.js +20 -0
  118. package/dist/serve/worker/ai-proxy-paths.js.map +1 -0
  119. package/dist/serve/worker/client/ai.d.ts +3 -1
  120. package/dist/serve/worker/client/ai.d.ts.map +1 -1
  121. package/dist/serve/worker/client/ai.js +3 -0
  122. package/dist/serve/worker/client/ai.js.map +1 -1
  123. package/dist/serve/worker/entry.js +2 -0
  124. package/dist/serve/worker/entry.js.map +1 -1
  125. package/dist/serve/worker/host-ai.d.ts +4 -5
  126. package/dist/serve/worker/host-ai.d.ts.map +1 -1
  127. package/dist/serve/worker/host-ai.js +19 -23
  128. package/dist/serve/worker/host-ai.js.map +1 -1
  129. package/dist/serve/worker/host-context.d.ts +13 -4
  130. package/dist/serve/worker/host-context.d.ts.map +1 -1
  131. package/dist/serve/worker/host-context.js.map +1 -1
  132. package/dist/serve/worker/inbound-validation/operation-arguments.d.ts.map +1 -1
  133. package/dist/serve/worker/inbound-validation/operation-arguments.js +11 -0
  134. package/dist/serve/worker/inbound-validation/operation-arguments.js.map +1 -1
  135. package/dist/serve/worker/protocol.d.ts +13 -0
  136. package/dist/serve/worker/protocol.d.ts.map +1 -1
  137. package/dist/serve/worker/protocol.js.map +1 -1
  138. package/package.json +4 -4
  139. package/src/assurance/.generated/conformance-verdicts.ts +8 -0
  140. package/src/bridge/server/peer.ts +49 -1
  141. package/src/cli/serve.ts +14 -2
  142. package/src/conformance/.generated/can-i-use-browser.ts +2 -2
  143. package/src/conformance/.generated/can-i-use.ts +1 -1
  144. package/src/conformance/.generated/conformance-docs.ts +1 -1
  145. package/src/register/app-bridge-admin-app-check.ts +46 -0
  146. package/src/register/app-bridge-app-check.ts +4 -0
  147. package/src/register/app-bridge-shadow.ts +35 -5
  148. package/src/register/index.ts +23 -0
  149. package/src/serve/ai-diagnostics-relay.ts +6 -1
  150. package/src/serve/ai-proxy.ts +43 -14
  151. package/src/serve/ai-terminal-blocks.ts +8 -4
  152. package/src/serve/ai-terminal-text.ts +11 -0
  153. package/src/serve/bundler.ts +163 -2
  154. package/src/serve/entries/ai.ts +28 -27
  155. package/src/serve/entries/app-ai-passthrough.ts +8 -58
  156. package/src/serve/firebase-module-swap.ts +52 -3
  157. package/src/serve/gemini-error-redaction.ts +60 -0
  158. package/src/serve/gemini-proxy.ts +140 -0
  159. package/src/serve/html-injection.ts +4 -2
  160. package/src/serve/namespace.ts +74 -15
  161. package/src/serve/sandbox-session.ts +4 -0
  162. package/src/serve/vite-sandbox-generation.ts +2 -0
  163. package/src/serve/worker/ai-proxy-paths.ts +35 -0
  164. package/src/serve/worker/client/ai.ts +6 -1
  165. package/src/serve/worker/entry.ts +2 -0
  166. package/src/serve/worker/host-ai.ts +23 -23
  167. package/src/serve/worker/host-context.ts +13 -3
  168. package/src/serve/worker/inbound-validation/operation-arguments.ts +11 -0
  169. package/src/serve/worker/protocol.ts +14 -2
@@ -3,20 +3,34 @@
3
3
  <style>:root{--rhythm-para: .875rem;--rhythm-group: 1.5rem;--rhythm-subsection: 2.25rem;--rhythm-section: 3.25rem;--rhythm-list: calc(var(--rhythm-para) * .75)}.prose>*,.prose blockquote>*,.prose li>*,.prose .compat-stat>*{margin-block:0}.prose>*+*{margin-block-start:var(--rhythm-group)}.prose>*+h2{margin-block-start:var(--rhythm-section)}.prose>*+h3,.prose>*+h4{margin-block-start:var(--rhythm-subsection)}.prose>h1+*,.prose>h2+*,.prose>h3+*,.prose>h4+*{margin-block-start:var(--rhythm-para)}.prose>p+p{margin-block-start:var(--rhythm-para)}.prose>p+ul,.prose>p+ol,.prose>p+blockquote{margin-block-start:var(--rhythm-para)}.prose li+li{margin-block-start:var(--rhythm-list)}.prose li>ul,.prose li>ol{margin-block-start:var(--rhythm-list)}.prose li>*+*,.prose blockquote>*+*{margin-block-start:var(--rhythm-para)}
4
4
  html{scrollbar-gutter:stable}
5
5
  </style></head> <body> <header class="studio__chrome"> <div class="studio__bar"> <div class="studio__chrome-inner"> <nav class="studio__nav" aria-label="Site"> <a class="studio__brand" href="/__pyric/ui/" aria-label="Pyric">pyric</a> <a class="studio__nav-tab" href="/__pyric/ui/"> Home </a><a class="studio__nav-tab" href="/__pyric/ui/docs" aria-current="page"> Docs </a><a class="studio__nav-tab" href="/__pyric/ui/studio"> Studio </a> </nav> </div> </div> </header> <div class="docs-shell" data-astro-cid-mw7aashj> <details class="docs-nav-drawer" data-astro-cid-mw7aashj> <summary data-astro-cid-mw7aashj>Navigate</summary> <nav class="docs-nav" aria-label="Documentation"> <div class="docs-search" data-index-url="/__pyric/ui/docs/index.json"> <div class="docs-search-field"> <svg class="docs-search-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> <path d="M11.5 11.5 15 15M13 7A6 6 0 1 1 1 7a6 6 0 0 1 12 0Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path> </svg> <input type="text" class="docs-search-input" placeholder="Search docs" aria-label="Search docs" autocomplete="off" spellcheck="false"> <kbd class="docs-search-kbd">⌘K</kbd> </div> <ul class="docs-search-results" hidden></ul> </div> <script type="module">let i=null;function d(t){return i||(i=fetch(t).then(e=>e.json())),i}function l(t,e){return t.title.toLowerCase().includes(e)||t.excerpt.toLowerCase().includes(e)?!0:t.headings.some(r=>r.text.toLowerCase().includes(e))}function u(t,e,r){if(e.innerHTML="",t.length===0){e.hidden=!1;{const n=document.createElement("li");n.className="docs-search-empty",n.textContent="No matches",e.appendChild(n)}return}e.hidden=!1;for(const n of t.slice(0,8)){const s=document.createElement("li"),c=document.createElement("a");c.href=n.path;const o=document.createElement("span");o.className="docs-search-result-title",o.textContent=n.title;const a=document.createElement("span");a.className="docs-search-result-meta",a.textContent=[n.group,n.section].filter(Boolean).join(" / "),c.append(o,a),s.appendChild(c),e.appendChild(s)}}document.querySelectorAll(".docs-search").forEach(t=>{const e=t.dataset.indexUrl,r=t.querySelector(".docs-search-input"),n=t.querySelector(".docs-search-results");r.addEventListener("input",async()=>{const s=r.value.trim().toLowerCase();if(!s){n.hidden=!0,n.innerHTML="";return}const{pages:c}=await d(e);r.value.trim().toLowerCase()===s&&u(c.filter(o=>l(o,s)),n)}),r.addEventListener("focus",()=>{d(e),n.childElementCount>0&&(n.hidden=!1)}),r.addEventListener("keydown",s=>{s.key==="Escape"&&(r.value="",n.hidden=!0,n.innerHTML="",r.blur())}),r.addEventListener("blur",()=>{window.setTimeout(()=>{n.hidden=!0},150)})});document.addEventListener("keydown",t=>{if(!(t.metaKey||t.ctrlKey)||t.key.toLowerCase()!=="k")return;const e=[...document.querySelectorAll(".docs-search-input")].find(r=>r.offsetParent!==null);e&&(t.preventDefault(),e.focus(),e.select())});</script> <details class="docs-nav-group"> <summary>Overview</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/overview/"> Overview </a> </li><li> <a href="/__pyric/ui/docs/tutorial/"> Tutorial </a> </li><li> <a href="/__pyric/ui/docs/examples/"> Examples </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Get started</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/get-started/start-building/"> Quickstart </a> </li><li> <a href="/__pyric/ui/docs/get-started/vite/"> Vite </a> </li><li> <a href="/__pyric/ui/docs/get-started/node-host/"> Node host </a> </li><li> <a href="/__pyric/ui/docs/get-started/nextjs/"> Next.js </a> </li><li> <a href="/__pyric/ui/docs/get-started/backend-bundlers/"> Backend bundlers </a> </li><li> <a href="/__pyric/ui/docs/get-started/flutter/"> Flutter </a> </li><li> <a href="/__pyric/ui/docs/get-started/how-the-swap-works/"> How the swap works </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" open> <summary>Build</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/build/authentication/"> Authentication </a> </li><li> <a href="/__pyric/ui/docs/build/default-avatars/"> Default avatars </a> </li><li> <a href="/__pyric/ui/docs/build/inspect-activity/"> Activity on the page </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-firestore/"> Cloud Firestore </a> </li><li> <a href="/__pyric/ui/docs/build/realtime-database/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-storage/"> Cloud Storage </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-messaging/"> Cloud Messaging </a> </li><li> <a href="/__pyric/ui/docs/build/ai-logic/" aria-current="page"> AI Logic </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Secure &amp; debug</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/secure/secure-it-with-rules/"> Security Rules </a> </li><li> <a href="/__pyric/ui/docs/secure/simulate-and-lint/"> Simulate and lint before you deploy </a> </li><li> <a href="/__pyric/ui/docs/secure/write-a-rules-test-suite/"> Write a rules test suite </a> </li><li> <a href="/__pyric/ui/docs/secure/read-a-denial/"> Read a denial and understand it </a> </li><li> <a href="/__pyric/ui/docs/secure/rules-standard-library/"> The rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-in-typescript/"> RTDB rules in TypeScript </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-standard-library/"> RTDB rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/firestore-rules-limits/"> Rules limits </a> </li><li> <a href="/__pyric/ui/docs/secure/audit-your-rules/"> Audit rules and data </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading"> Examples </h2> <ul> <li> <a href="/__pyric/ui/docs/examples/chess/"> Chess showcase </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Observe &amp; shape</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/observe/see-whats-happening/"> Traffic &amp; rule verdicts </a> </li><li> <a href="/__pyric/ui/docs/observe/resolve-runtime-status/"> Runtime status </a> </li><li> <a href="/__pyric/ui/docs/observe/switch-and-impersonate-identities/"> Impersonate &amp; tenants </a> </li><li> <a href="/__pyric/ui/docs/observe/shape-your-data/"> Seed, snapshot, replay </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Ship &amp; test</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/ship/ship-to-production/"> Ship to production </a> </li><li> <a href="/__pyric/ui/docs/ship/test-in-node/"> Test in Node </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Work with an agent</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/agent/set-up-your-agent/"> Connect an agent </a> </li><li> <a href="/__pyric/ui/docs/agent/work-with-an-agent/"> Use the MCP tools </a> </li><li> <a href="/__pyric/ui/docs/agent/watch-and-review/"> Watch in Pyric Studio </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Trust</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/trust/versioning-and-compatibility/"> Versioning and the fb tag </a> </li><li> <a href="/__pyric/ui/docs/trust/rules-standard-library/"> Rules standard library reference </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Conformance</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/conformance-scores/"> Conformance scores </a> </li><li> <a href="/__pyric/ui/docs/trust/how-we-know-it-matches-firebase/"> How we know it matches Firebase </a> </li><li> <a href="/__pyric/ui/docs/app-compat/"> App </a> </li><li> <a href="/__pyric/ui/docs/ai-compat/"> AI Logic </a> </li><li> <a href="/__pyric/ui/docs/auth-compat/"> Auth </a> </li><li> <a href="/__pyric/ui/docs/firestore-compat/"> Firestore </a> </li><li> <a href="/__pyric/ui/docs/database-compat/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/storage-compat/"> Storage </a> </li><li> <a href="/__pyric/ui/docs/messaging-compat/"> Messaging </a> </li><li> <a href="/__pyric/ui/docs/app-check-compat/"> App Check </a> </li><li> <a href="/__pyric/ui/docs/functions-rtdb-compat/"> Functions · RTDB </a> </li><li> <a href="/__pyric/ui/docs/rules-compat/"> Rules </a> </li><li> <a href="/__pyric/ui/docs/firestore-flutter-compat/"> Firestore · Flutter </a> </li><li> <a href="/__pyric/ui/docs/firestore-kotlin-compat/"> Firestore · Kotlin </a> </li><li> <a href="/__pyric/ui/docs/firestore-swift-compat/"> Firestore · Swift </a> </li><li> <a href="/__pyric/ui/docs/auth-flutter-compat/"> Auth · Flutter </a> </li><li> <a href="/__pyric/ui/docs/auth-kotlin-compat/"> Auth · Kotlin </a> </li><li> <a href="/__pyric/ui/docs/auth-swift-compat/"> FirebaseAuth Swift </a> </li><li> <a href="/__pyric/ui/docs/rtdb-flutter-compat/"> Realtime Database · Flutter </a> </li><li> <a href="/__pyric/ui/docs/rtdb-kotlin-compat/"> Realtime Database · Kotlin </a> </li><li> <a href="/__pyric/ui/docs/rtdb-swift-compat/"> Realtime Database · Swift </a> </li> </ul> </div> </div> </details> <details class="docs-nav-group"> <summary>Reference</summary> <div class="docs-nav-group-body docs-nav-group-body--reference"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/api-reference/"> API reference </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-app-reference-api/"> pyric/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-reference-api/"> pyric/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-lite-reference-api/"> pyric/firestore/lite </a> </li><li> <a href="/__pyric/ui/docs/pyric-auth-reference-api/"> pyric/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-reference-api/"> pyric/ai </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-scripting-reference-api/"> pyric/ai/scripting </a> </li><li> <a href="/__pyric/ui/docs/pyric-database-reference-api/"> pyric/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-reference-api/"> pyric/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-sw-reference-api/"> pyric/storage/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-reference-api/"> pyric/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-sw-reference-api/"> pyric/messaging/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-rules-reference-api/"> pyric/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-reference-api/"> pyric/sandbox </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-branches-store-reference-api/"> pyric/sandbox/branches/store </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-checkpoints-reference-api/"> pyric/sandbox/checkpoints </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-checkpoints-directory-reference-api/"> pyric/sandbox/checkpoints/directory </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-firestore-reference-api/"> pyric/sandbox/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-database-reference-api/"> pyric/sandbox/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-app-check-reference-api/"> pyric/app-check </a> </li><li> <a href="/__pyric/ui/docs/pyric-functions-reference-api/"> pyric/functions </a> </li><li> <a href="/__pyric/ui/docs/pyric-analytics-reference-api/"> pyric/analytics </a> </li><li> <a href="/__pyric/ui/docs/pyric-performance-reference-api/"> pyric/performance </a> </li><li> <a href="/__pyric/ui/docs/pyric-remote-config-reference-api/"> pyric/remote-config </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric-admin</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-admin-reference-api/"> pyric-admin </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-app-reference-api/"> pyric-admin/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-firestore-reference-api/"> pyric-admin/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-auth-reference-api/"> pyric-admin/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-database-reference-api/"> pyric-admin/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-storage-reference-api/"> pyric-admin/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-messaging-reference-api/"> pyric-admin/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-app-check-reference-api/"> pyric-admin/app-check </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-data-connect-reference-api/"> pyric-admin/data-connect </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-eventarc-reference-api/"> pyric-admin/eventarc </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-extensions-reference-api/"> pyric-admin/extensions </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-functions-reference-api/"> pyric-admin/functions </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-installations-reference-api/"> pyric-admin/installations </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-instance-id-reference-api/"> pyric-admin/instance-id </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-machine-learning-reference-api/"> pyric-admin/machine-learning </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-phone-number-verification-reference-api/"> pyric-admin/phone-number-verification </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-project-management-reference-api/"> pyric-admin/project-management </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-remote-config-reference-api/"> pyric-admin/remote-config </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-security-rules-reference-api/"> pyric-admin/security-rules </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">create-pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/create-pyric-reference-api/"> create-pyric </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-cli-credentials-node-reference-api/"> @pyric/cli/credentials/node </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-verify-reference-api/"> @pyric/cli/verify </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-repro-reference-api/"> @pyric/cli/repro </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-reference-api/"> @pyric/cli/conformance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-browser-reference-api/"> @pyric/cli/conformance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-docs-reference-api/"> @pyric/cli/conformance/docs </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-reference-api/"> @pyric/cli/assurance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-browser-reference-api/"> @pyric/cli/assurance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-reference-api/"> @pyric/cli/bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-client-reference-api/"> @pyric/cli/bridge/client </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-discover-reference-api/"> @pyric/cli/discover </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-reference-api/"> @pyric/cli/vite </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-ssr-loader-reference-api/"> @pyric/cli/vite/ssr-loader </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-next-reference-api/"> @pyric/cli/next </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bundler-reference-api/"> @pyric/cli/bundler </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-serve-worker-reference-api/"> @pyric/cli/serve/worker </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-remote-reference-api/"> @pyric/cli/remote </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-reference-api/"> @pyric/cli/register </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-app-bridge-reference-api/"> @pyric/cli/register/app-bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-app-bridge-app-check-reference-api/"> @pyric/cli/register/app-bridge-app-check </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-app-reference-api/"> @pyric/cli/register/client/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-app-bridge-reference-api/"> @pyric/cli/register/client/app-bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-auth-reference-api/"> @pyric/cli/register/client/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-database-reference-api/"> @pyric/cli/register/client/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-firestore-reference-api/"> @pyric/cli/register/client/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-storage-reference-api/"> @pyric/cli/register/client/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-flow-reference-api/"> @pyric/cli/flow </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/ui</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-ui-auth-reference-api/"> @pyric/ui/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-auth-hooks-reference-api/"> @pyric/ui/auth/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-primitives-reference-api/"> @pyric/ui/primitives </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-listener-owner-reference-api/"> @pyric/ui/listener-owner </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-reference-api/"> @pyric/ui/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-hooks-reference-api/"> @pyric/ui/firestore/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rtdb-reference-api/"> @pyric/ui/rtdb </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-reference-api/"> @pyric/ui/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-hooks-reference-api/"> @pyric/ui/storage/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-reference-api/"> @pyric/ui/traffic </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-hooks-reference-api/"> @pyric/ui/traffic/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-reference-api/"> @pyric/ui/events </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-hooks-reference-api/"> @pyric/ui/events/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-agents-reference-api/"> @pyric/ui/agents </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-reference-api/"> @pyric/ui/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-hooks-reference-api/"> @pyric/ui/rules/hooks </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/reference/cli/"> CLI </a> </li> </ul> </div> </div> </details> </nav> </details> <nav class="docs-nav" aria-label="Documentation"> <div class="docs-search" data-index-url="/__pyric/ui/docs/index.json"> <div class="docs-search-field"> <svg class="docs-search-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> <path d="M11.5 11.5 15 15M13 7A6 6 0 1 1 1 7a6 6 0 0 1 12 0Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path> </svg> <input type="text" class="docs-search-input" placeholder="Search docs" aria-label="Search docs" autocomplete="off" spellcheck="false"> <kbd class="docs-search-kbd">⌘K</kbd> </div> <ul class="docs-search-results" hidden></ul> </div> <details class="docs-nav-group" id="nav-group-overview"> <summary>Overview</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/overview/"> Overview </a> </li><li> <a href="/__pyric/ui/docs/tutorial/"> Tutorial </a> </li><li> <a href="/__pyric/ui/docs/examples/"> Examples </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-get-started"> <summary>Get started</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/get-started/start-building/"> Quickstart </a> </li><li> <a href="/__pyric/ui/docs/get-started/vite/"> Vite </a> </li><li> <a href="/__pyric/ui/docs/get-started/node-host/"> Node host </a> </li><li> <a href="/__pyric/ui/docs/get-started/nextjs/"> Next.js </a> </li><li> <a href="/__pyric/ui/docs/get-started/backend-bundlers/"> Backend bundlers </a> </li><li> <a href="/__pyric/ui/docs/get-started/flutter/"> Flutter </a> </li><li> <a href="/__pyric/ui/docs/get-started/how-the-swap-works/"> How the swap works </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-build" open> <summary>Build</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/build/authentication/"> Authentication </a> </li><li> <a href="/__pyric/ui/docs/build/default-avatars/"> Default avatars </a> </li><li> <a href="/__pyric/ui/docs/build/inspect-activity/"> Activity on the page </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-firestore/"> Cloud Firestore </a> </li><li> <a href="/__pyric/ui/docs/build/realtime-database/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-storage/"> Cloud Storage </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-messaging/"> Cloud Messaging </a> </li><li> <a href="/__pyric/ui/docs/build/ai-logic/" aria-current="page"> AI Logic </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-secure-debug"> <summary>Secure &amp; debug</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/secure/secure-it-with-rules/"> Security Rules </a> </li><li> <a href="/__pyric/ui/docs/secure/simulate-and-lint/"> Simulate and lint before you deploy </a> </li><li> <a href="/__pyric/ui/docs/secure/write-a-rules-test-suite/"> Write a rules test suite </a> </li><li> <a href="/__pyric/ui/docs/secure/read-a-denial/"> Read a denial and understand it </a> </li><li> <a href="/__pyric/ui/docs/secure/rules-standard-library/"> The rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-in-typescript/"> RTDB rules in TypeScript </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-standard-library/"> RTDB rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/firestore-rules-limits/"> Rules limits </a> </li><li> <a href="/__pyric/ui/docs/secure/audit-your-rules/"> Audit rules and data </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading" id="nav-section-secure-debug-examples"> Examples </h2> <ul> <li> <a href="/__pyric/ui/docs/examples/chess/"> Chess showcase </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-observe-shape"> <summary>Observe &amp; shape</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/observe/see-whats-happening/"> Traffic &amp; rule verdicts </a> </li><li> <a href="/__pyric/ui/docs/observe/resolve-runtime-status/"> Runtime status </a> </li><li> <a href="/__pyric/ui/docs/observe/switch-and-impersonate-identities/"> Impersonate &amp; tenants </a> </li><li> <a href="/__pyric/ui/docs/observe/shape-your-data/"> Seed, snapshot, replay </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-ship-test"> <summary>Ship &amp; test</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/ship/ship-to-production/"> Ship to production </a> </li><li> <a href="/__pyric/ui/docs/ship/test-in-node/"> Test in Node </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-work-with-an-agent"> <summary>Work with an agent</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/agent/set-up-your-agent/"> Connect an agent </a> </li><li> <a href="/__pyric/ui/docs/agent/work-with-an-agent/"> Use the MCP tools </a> </li><li> <a href="/__pyric/ui/docs/agent/watch-and-review/"> Watch in Pyric Studio </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-trust"> <summary>Trust</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/trust/versioning-and-compatibility/"> Versioning and the fb tag </a> </li><li> <a href="/__pyric/ui/docs/trust/rules-standard-library/"> Rules standard library reference </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-conformance"> <summary>Conformance</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/conformance-scores/"> Conformance scores </a> </li><li> <a href="/__pyric/ui/docs/trust/how-we-know-it-matches-firebase/"> How we know it matches Firebase </a> </li><li> <a href="/__pyric/ui/docs/app-compat/"> App </a> </li><li> <a href="/__pyric/ui/docs/ai-compat/"> AI Logic </a> </li><li> <a href="/__pyric/ui/docs/auth-compat/"> Auth </a> </li><li> <a href="/__pyric/ui/docs/firestore-compat/"> Firestore </a> </li><li> <a href="/__pyric/ui/docs/database-compat/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/storage-compat/"> Storage </a> </li><li> <a href="/__pyric/ui/docs/messaging-compat/"> Messaging </a> </li><li> <a href="/__pyric/ui/docs/app-check-compat/"> App Check </a> </li><li> <a href="/__pyric/ui/docs/functions-rtdb-compat/"> Functions · RTDB </a> </li><li> <a href="/__pyric/ui/docs/rules-compat/"> Rules </a> </li><li> <a href="/__pyric/ui/docs/firestore-flutter-compat/"> Firestore · Flutter </a> </li><li> <a href="/__pyric/ui/docs/firestore-kotlin-compat/"> Firestore · Kotlin </a> </li><li> <a href="/__pyric/ui/docs/firestore-swift-compat/"> Firestore · Swift </a> </li><li> <a href="/__pyric/ui/docs/auth-flutter-compat/"> Auth · Flutter </a> </li><li> <a href="/__pyric/ui/docs/auth-kotlin-compat/"> Auth · Kotlin </a> </li><li> <a href="/__pyric/ui/docs/auth-swift-compat/"> FirebaseAuth Swift </a> </li><li> <a href="/__pyric/ui/docs/rtdb-flutter-compat/"> Realtime Database · Flutter </a> </li><li> <a href="/__pyric/ui/docs/rtdb-kotlin-compat/"> Realtime Database · Kotlin </a> </li><li> <a href="/__pyric/ui/docs/rtdb-swift-compat/"> Realtime Database · Swift </a> </li> </ul> </div> </div> </details> <details class="docs-nav-group" id="nav-group-reference"> <summary>Reference</summary> <div class="docs-nav-group-body docs-nav-group-body--reference"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/api-reference/"> API reference </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-app-reference-api/"> pyric/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-reference-api/"> pyric/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-lite-reference-api/"> pyric/firestore/lite </a> </li><li> <a href="/__pyric/ui/docs/pyric-auth-reference-api/"> pyric/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-reference-api/"> pyric/ai </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-scripting-reference-api/"> pyric/ai/scripting </a> </li><li> <a href="/__pyric/ui/docs/pyric-database-reference-api/"> pyric/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-reference-api/"> pyric/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-sw-reference-api/"> pyric/storage/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-reference-api/"> pyric/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-sw-reference-api/"> pyric/messaging/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-rules-reference-api/"> pyric/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-reference-api/"> pyric/sandbox </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-branches-store-reference-api/"> pyric/sandbox/branches/store </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-checkpoints-reference-api/"> pyric/sandbox/checkpoints </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-checkpoints-directory-reference-api/"> pyric/sandbox/checkpoints/directory </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-firestore-reference-api/"> pyric/sandbox/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-database-reference-api/"> pyric/sandbox/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-app-check-reference-api/"> pyric/app-check </a> </li><li> <a href="/__pyric/ui/docs/pyric-functions-reference-api/"> pyric/functions </a> </li><li> <a href="/__pyric/ui/docs/pyric-analytics-reference-api/"> pyric/analytics </a> </li><li> <a href="/__pyric/ui/docs/pyric-performance-reference-api/"> pyric/performance </a> </li><li> <a href="/__pyric/ui/docs/pyric-remote-config-reference-api/"> pyric/remote-config </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric-admin</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-admin-reference-api/"> pyric-admin </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-app-reference-api/"> pyric-admin/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-firestore-reference-api/"> pyric-admin/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-auth-reference-api/"> pyric-admin/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-database-reference-api/"> pyric-admin/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-storage-reference-api/"> pyric-admin/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-messaging-reference-api/"> pyric-admin/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-app-check-reference-api/"> pyric-admin/app-check </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-data-connect-reference-api/"> pyric-admin/data-connect </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-eventarc-reference-api/"> pyric-admin/eventarc </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-extensions-reference-api/"> pyric-admin/extensions </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-functions-reference-api/"> pyric-admin/functions </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-installations-reference-api/"> pyric-admin/installations </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-instance-id-reference-api/"> pyric-admin/instance-id </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-machine-learning-reference-api/"> pyric-admin/machine-learning </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-phone-number-verification-reference-api/"> pyric-admin/phone-number-verification </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-project-management-reference-api/"> pyric-admin/project-management </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-remote-config-reference-api/"> pyric-admin/remote-config </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-security-rules-reference-api/"> pyric-admin/security-rules </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">create-pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/create-pyric-reference-api/"> create-pyric </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-cli-credentials-node-reference-api/"> @pyric/cli/credentials/node </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-verify-reference-api/"> @pyric/cli/verify </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-repro-reference-api/"> @pyric/cli/repro </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-reference-api/"> @pyric/cli/conformance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-browser-reference-api/"> @pyric/cli/conformance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-docs-reference-api/"> @pyric/cli/conformance/docs </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-reference-api/"> @pyric/cli/assurance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-browser-reference-api/"> @pyric/cli/assurance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-reference-api/"> @pyric/cli/bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-client-reference-api/"> @pyric/cli/bridge/client </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-discover-reference-api/"> @pyric/cli/discover </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-reference-api/"> @pyric/cli/vite </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-ssr-loader-reference-api/"> @pyric/cli/vite/ssr-loader </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-next-reference-api/"> @pyric/cli/next </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bundler-reference-api/"> @pyric/cli/bundler </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-serve-worker-reference-api/"> @pyric/cli/serve/worker </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-remote-reference-api/"> @pyric/cli/remote </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-reference-api/"> @pyric/cli/register </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-app-bridge-reference-api/"> @pyric/cli/register/app-bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-app-bridge-app-check-reference-api/"> @pyric/cli/register/app-bridge-app-check </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-app-reference-api/"> @pyric/cli/register/client/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-app-bridge-reference-api/"> @pyric/cli/register/client/app-bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-auth-reference-api/"> @pyric/cli/register/client/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-database-reference-api/"> @pyric/cli/register/client/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-firestore-reference-api/"> @pyric/cli/register/client/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-client-storage-reference-api/"> @pyric/cli/register/client/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-flow-reference-api/"> @pyric/cli/flow </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/ui</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-ui-auth-reference-api/"> @pyric/ui/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-auth-hooks-reference-api/"> @pyric/ui/auth/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-primitives-reference-api/"> @pyric/ui/primitives </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-listener-owner-reference-api/"> @pyric/ui/listener-owner </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-reference-api/"> @pyric/ui/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-hooks-reference-api/"> @pyric/ui/firestore/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rtdb-reference-api/"> @pyric/ui/rtdb </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-reference-api/"> @pyric/ui/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-hooks-reference-api/"> @pyric/ui/storage/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-reference-api/"> @pyric/ui/traffic </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-hooks-reference-api/"> @pyric/ui/traffic/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-reference-api/"> @pyric/ui/events </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-hooks-reference-api/"> @pyric/ui/events/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-agents-reference-api/"> @pyric/ui/agents </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-reference-api/"> @pyric/ui/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-hooks-reference-api/"> @pyric/ui/rules/hooks </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/reference/cli/"> CLI </a> </li> </ul> </div> </div> </details> </nav> <main class="doc-main" data-astro-cid-mw7aashj> <nav class="docs-breadcrumbs" aria-label="Breadcrumb"> <ol> <li> <a href="/__pyric/ui/docs/build/authentication/">Build</a> </li><li> <span class="docs-breadcrumbs-sep" aria-hidden="true">/</span> <span aria-current="page">AI Logic</span> </li> </ol> </nav> <article class="prose" data-astro-cid-mw7aashj> <h1 id="run-firebase-ai-logic-locally">Run Firebase AI Logic locally</h1>
6
- <p>Keep using the Firebase AI Logic Web API:</p>
7
- <pre class="astro-code one-dark-pro" style="background-color:#282c34;color:#abb2bf; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">getAI</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">getGenerativeModel</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'firebase/ai'</span><span style="color:#ABB2BF">;</span></span>
6
+ <p>Keep using the Firebase AI Logic Web API, with App Check initialized first. During development, Pyric answers through a local engine instead of a Google AI endpoint. A normal production build runs the same application code through Firebase. Use the <a href="https://firebase.google.com/docs/ai-logic/get-started?platform=web" target="_blank" rel="noopener noreferrer" class="external-link">Firebase AI Logic Web guide</a> for the application API.</p>
7
+ <h2 id="initialize-app-check-before-getai">Initialize App Check before getAI</h2>
8
+ <p>From November 2, 2026, Firebase AI Logic requires App Check: a request without an App Check token gets a 403. <code>getAI</code> reads App Check when it starts, so call <code>initializeAppCheck</code> before <code>getAI</code>, synchronously, with a static import. A model created before App Check sends no token.</p>
9
+ <pre class="astro-code one-dark-pro" style="background-color:#282c34;color:#abb2bf; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">initializeAppCheck</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">ReCaptchaV3Provider</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'firebase/app-check'</span><span style="color:#ABB2BF">;</span></span>
10
+ <span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">getAI</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">getGenerativeModel</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'firebase/ai'</span><span style="color:#ABB2BF">;</span></span>
11
+ <span class="line"></span>
12
+ <span class="line"><span style="color:#C678DD">if</span><span style="color:#ABB2BF"> (</span><span style="color:#C678DD">import</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">meta</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">env</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">DEV</span><span style="color:#ABB2BF">) {</span></span>
13
+ <span class="line"><span style="color:#E5C07B"> self</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">FIREBASE_APPCHECK_DEBUG_TOKEN</span><span style="color:#56B6C2"> =</span><span style="color:#D19A66"> true</span><span style="color:#ABB2BF">;</span></span>
14
+ <span class="line"><span style="color:#ABB2BF">}</span></span>
15
+ <span class="line"><span style="color:#61AFEF">initializeAppCheck</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">app</span><span style="color:#ABB2BF">, {</span></span>
16
+ <span class="line"><span style="color:#E06C75"> provider</span><span style="color:#ABB2BF">: </span><span style="color:#C678DD">new</span><span style="color:#61AFEF"> ReCaptchaV3Provider</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'your-recaptcha-v3-site-key'</span><span style="color:#ABB2BF">),</span></span>
17
+ <span class="line"><span style="color:#E06C75"> isTokenAutoRefreshEnabled</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span>
18
+ <span class="line"><span style="color:#ABB2BF">});</span></span>
8
19
  <span class="line"></span>
9
20
  <span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> ai</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> getAI</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">app</span><span style="color:#ABB2BF">);</span></span>
10
21
  <span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> model</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> getGenerativeModel</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">ai</span><span style="color:#ABB2BF">, {</span></span>
11
22
  <span class="line"><span style="color:#E06C75"> model</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">'gemini-flash-lite-latest'</span><span style="color:#ABB2BF">,</span></span>
12
23
  <span class="line"><span style="color:#ABB2BF">});</span></span>
13
24
  <span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> result</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> await</span><span style="color:#E5C07B"> model</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">generateContent</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'Summarise this report.'</span><span style="color:#ABB2BF">);</span></span></code></pre>
14
- <p>During development, Pyric answers through a local engine instead of a Google AI endpoint. A normal production build runs the same application code through Firebase. Use the <a href="https://firebase.google.com/docs/ai-logic/get-started?platform=web" target="_blank" rel="noopener noreferrer" class="external-link">Firebase AI Logic Web guide</a> for the application API.</p>
25
+ <p>Setting <code>FIREBASE_APPCHECK_DEBUG_TOKEN</code> in development switches App Check to its debug provider, so local runs need no reCAPTCHA. In the sandbox, Pyric answers App Check locally. In <a href="#pass-through-to-production-ai-models">production AI mode</a> the Firebase SDK prints a debug token to the browser console; register it in the Firebase console under App Check, Apps, Manage debug tokens, and AI Logic accepts the requests. Get the reCAPTCHA v3 site key for production builds from the same App Check page.</p>
26
+ <p>The sandbox refuses these requests too. If an app never calls <code>initializeAppCheck</code>, or calls it after <code>getAI</code>, <code>generateContent</code>, <code>generateContentStream</code>, <code>countTokens</code> and chat messages fail with the same 403 error production returns. Pyric prints how to fix it in the browser console and in the dev server terminal. Sandbox tokens from <code>initializeAppCheck</code> are accepted, with or without the debug provider.</p>
15
27
  <h2 id="start-with-deterministic-responses">Start with deterministic responses</h2>
16
28
  <p>With no AI configuration, Pyric uses its built-in scripted engine. It makes no network requests and gives deterministic responses. Tests can queue an exact response through the sandbox-only scripting entry point:</p>
17
- <pre class="astro-code one-dark-pro" style="background-color:#282c34;color:#abb2bf; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">getAI</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'pyric/ai'</span><span style="color:#ABB2BF">;</span></span>
29
+ <pre class="astro-code one-dark-pro" style="background-color:#282c34;color:#abb2bf; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">initializeAppCheck</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">ReCaptchaV3Provider</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'firebase/app-check'</span><span style="color:#ABB2BF">;</span></span>
30
+ <span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">getAI</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'pyric/ai'</span><span style="color:#ABB2BF">;</span></span>
18
31
  <span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> { </span><span style="color:#E06C75">script</span><span style="color:#ABB2BF"> } </span><span style="color:#C678DD">from</span><span style="color:#98C379"> 'pyric/ai/scripting'</span><span style="color:#ABB2BF">;</span></span>
19
32
  <span class="line"></span>
33
+ <span class="line"><span style="color:#61AFEF">initializeAppCheck</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">app</span><span style="color:#ABB2BF">, { </span><span style="color:#E06C75">provider</span><span style="color:#ABB2BF">: </span><span style="color:#C678DD">new</span><span style="color:#61AFEF"> ReCaptchaV3Provider</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'your-recaptcha-v3-site-key'</span><span style="color:#ABB2BF">) });</span></span>
20
34
  <span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> ai</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> getAI</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">app</span><span style="color:#ABB2BF">);</span></span>
21
35
  <span class="line"><span style="color:#61AFEF">script</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">ai</span><span style="color:#ABB2BF">, [</span></span>
22
36
  <span class="line"><span style="color:#ABB2BF"> {</span></span>
@@ -80,7 +94,9 @@ application’s job.</p>
80
94
  <p>The plugin option takes precedence over <code>PYRIC_AI_MODE</code>.</p>
81
95
  <p>In production mode, Pyric stops intercepting <code>getAI()</code> and <code>getGenerativeModel()</code> calls with local sandbox responses or same-origin proxies. <code>firebase/ai</code> is the Firebase SDK, and it calls Google AI or Vertex AI with the <code>apiKey</code> and <code>projectId</code> your app passes to <code>initializeApp</code>. The dev server needs no Gemini API key. The startup banner confirms the mode:</p>
82
96
  <pre class="astro-code one-dark-pro" style="background-color:#282c34;color:#abb2bf; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>✔ [pyric] ai: production passthrough; firebase/ai is the Firebase SDK, calling Google AI or Vertex AI with the app's initializeApp config</span></span></code></pre>
97
+ <p>Production mode works the same way on <code>pyric sandbox</code>, on the Vite plugin, and in a Node process started with <code>node --import @pyric/cli/register</code>. On <code>pyric sandbox</code> without Vite, Pyric builds <code>firebase/app</code>, <code>firebase/ai</code>, and <code>firebase/app-check</code> from your project’s own <code>firebase</code> package, so install <code>firebase</code> in the project first. Without it, <code>pyric sandbox</code> stops at startup and says to install it. Upgrading <code>firebase</code> rebuilds that bundle on the next start.</p>
83
98
  <p>The model id you request goes to the backend unchanged. A model the backend does not serve fails with the backend’s own error, as it would in production, and <code>modelVersion</code> is the value the backend reports. Other Firebase modules, such as <code>firebase/firestore</code> and <code>firebase/auth</code>, still run against the local sandbox.</p>
99
+ <p>In production mode the page’s App Check tokens come from Firebase. A server started with <code>node --import @pyric/cli/register</code> and <code>PYRIC_AI_MODE=production</code> verifies them with <code>firebase-admin</code>, so install <code>firebase-admin</code> beside the server. Verification fetches Firebase’s public keys and uses no credentials, and <code>verifyToken(token, { consume: true })</code> is recorded in the server process, not by Firebase. Tokens the sandbox minted still verify. In sandbox AI mode, a token from Firebase is refused with an <code>app-check/invalid-argument</code> error that says why.</p>
84
100
  <h3 id="project-configuration-requirements">Project configuration requirements</h3>
85
101
  <p>To use production pass-through successfully, your Firebase project configuration must be authorized for live cloud services:</p>
86
102
  <ol>
@@ -90,4 +106,4 @@ application’s job.</p>
90
106
  </ol>
91
107
  <h2 id="check-the-supported-boundary">Check the supported boundary</h2>
92
108
  <p>Per-feature support is tracked on the <a href="../../ai-compat/">AI Logic conformance page</a>.</p>
93
- <p>Continue with <a href="../../observe/see-whats-happening/">Inspect and correct</a> or <a href="../../ship/ship-to-production/">ship unchanged</a>.</p> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#start-with-deterministic-responses" data-astro-cid-mw7aashj>Start with deterministic responses</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#use-a-local-model" data-astro-cid-mw7aashj>Use a local model</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#point-pyric-at-another-model-server" data-astro-cid-mw7aashj>Point Pyric at another model server</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#set-the-variables-for-one-command" data-astro-cid-mw7aashj>Set the variables for one command</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#configure-the-vite-plugin-explicitly" data-astro-cid-mw7aashj>Configure the Vite plugin explicitly</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#pass-through-to-production-ai-models" data-astro-cid-mw7aashj>Pass through to production AI models</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#project-configuration-requirements" data-astro-cid-mw7aashj>Project configuration requirements</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#check-the-supported-boundary" data-astro-cid-mw7aashj>Check the supported boundary</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
109
+ <p>Continue with <a href="../../observe/see-whats-happening/">Inspect and correct</a> or <a href="../../ship/ship-to-production/">ship unchanged</a>.</p> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#initialize-app-check-before-getai" data-astro-cid-mw7aashj>Initialize App Check before getAI</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#start-with-deterministic-responses" data-astro-cid-mw7aashj>Start with deterministic responses</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#use-a-local-model" data-astro-cid-mw7aashj>Use a local model</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#point-pyric-at-another-model-server" data-astro-cid-mw7aashj>Point Pyric at another model server</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#set-the-variables-for-one-command" data-astro-cid-mw7aashj>Set the variables for one command</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#configure-the-vite-plugin-explicitly" data-astro-cid-mw7aashj>Configure the Vite plugin explicitly</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#pass-through-to-production-ai-models" data-astro-cid-mw7aashj>Pass through to production AI models</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#project-configuration-requirements" data-astro-cid-mw7aashj>Project configuration requirements</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#check-the-supported-boundary" data-astro-cid-mw7aashj>Check the supported boundary</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
@@ -1,11 +1,24 @@
1
1
 
2
2
  # Run Firebase AI Logic locally
3
3
 
4
- Keep using the Firebase AI Logic Web API:
4
+ Keep using the Firebase AI Logic Web API, with App Check initialized first. During development, Pyric answers through a local engine instead of a Google AI endpoint. A normal production build runs the same application code through Firebase. Use the [Firebase AI Logic Web guide](https://firebase.google.com/docs/ai-logic/get-started?platform=web) for the application API.
5
+
6
+ ## Initialize App Check before getAI
7
+
8
+ From November 2, 2026, Firebase AI Logic requires App Check: a request without an App Check token gets a 403. `getAI` reads App Check when it starts, so call `initializeAppCheck` before `getAI`, synchronously, with a static import. A model created before App Check sends no token.
5
9
 
6
10
  ```ts
11
+ import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
7
12
  import { getAI, getGenerativeModel } from 'firebase/ai';
8
13
 
14
+ if (import.meta.env.DEV) {
15
+ self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
16
+ }
17
+ initializeAppCheck(app, {
18
+ provider: new ReCaptchaV3Provider('your-recaptcha-v3-site-key'),
19
+ isTokenAutoRefreshEnabled: true,
20
+ });
21
+
9
22
  const ai = getAI(app);
10
23
  const model = getGenerativeModel(ai, {
11
24
  model: 'gemini-flash-lite-latest',
@@ -13,16 +26,20 @@ const model = getGenerativeModel(ai, {
13
26
  const result = await model.generateContent('Summarise this report.');
14
27
  ```
15
28
 
16
- During development, Pyric answers through a local engine instead of a Google AI endpoint. A normal production build runs the same application code through Firebase. Use the [Firebase AI Logic Web guide](https://firebase.google.com/docs/ai-logic/get-started?platform=web) for the application API.
29
+ Setting `FIREBASE_APPCHECK_DEBUG_TOKEN` in development switches App Check to its debug provider, so local runs need no reCAPTCHA. In the sandbox, Pyric answers App Check locally. In [production AI mode](#pass-through-to-production-ai-models) the Firebase SDK prints a debug token to the browser console; register it in the Firebase console under App Check, Apps, Manage debug tokens, and AI Logic accepts the requests. Get the reCAPTCHA v3 site key for production builds from the same App Check page.
30
+
31
+ The sandbox refuses these requests too. If an app never calls `initializeAppCheck`, or calls it after `getAI`, `generateContent`, `generateContentStream`, `countTokens` and chat messages fail with the same 403 error production returns. Pyric prints how to fix it in the browser console and in the dev server terminal. Sandbox tokens from `initializeAppCheck` are accepted, with or without the debug provider.
17
32
 
18
33
  ## Start with deterministic responses
19
34
 
20
35
  With no AI configuration, Pyric uses its built-in scripted engine. It makes no network requests and gives deterministic responses. Tests can queue an exact response through the sandbox-only scripting entry point:
21
36
 
22
37
  ```ts
38
+ import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';
23
39
  import { getAI } from 'pyric/ai';
24
40
  import { script } from 'pyric/ai/scripting';
25
41
 
42
+ initializeAppCheck(app, { provider: new ReCaptchaV3Provider('your-recaptcha-v3-site-key') });
26
43
  const ai = getAI(app);
27
44
  script(ai, [
28
45
  {
@@ -142,8 +159,12 @@ In production mode, Pyric stops intercepting `getAI()` and `getGenerativeModel()
142
159
  ✔ [pyric] ai: production passthrough; firebase/ai is the Firebase SDK, calling Google AI or Vertex AI with the app's initializeApp config
143
160
  ```
144
161
 
162
+ Production mode works the same way on `pyric sandbox`, on the Vite plugin, and in a Node process started with `node --import @pyric/cli/register`. On `pyric sandbox` without Vite, Pyric builds `firebase/app`, `firebase/ai`, and `firebase/app-check` from your project's own `firebase` package, so install `firebase` in the project first. Without it, `pyric sandbox` stops at startup and says to install it. Upgrading `firebase` rebuilds that bundle on the next start.
163
+
145
164
  The model id you request goes to the backend unchanged. A model the backend does not serve fails with the backend's own error, as it would in production, and `modelVersion` is the value the backend reports. Other Firebase modules, such as `firebase/firestore` and `firebase/auth`, still run against the local sandbox.
146
165
 
166
+ In production mode the page's App Check tokens come from Firebase. A server started with `node --import @pyric/cli/register` and `PYRIC_AI_MODE=production` verifies them with `firebase-admin`, so install `firebase-admin` beside the server. Verification fetches Firebase's public keys and uses no credentials, and `verifyToken(token, { consume: true })` is recorded in the server process, not by Firebase. Tokens the sandbox minted still verify. In sandbox AI mode, a token from Firebase is refused with an `app-check/invalid-argument` error that says why.
167
+
147
168
  ### Project configuration requirements
148
169
 
149
170
  To use production pass-through successfully, your Firebase project configuration must be authorized for live cloud services:
@@ -858,7 +858,7 @@ html{scrollbar-gutter:stable}
858
858
  <div class="compat-list">
859
859
  <details class="compat-row" data-status="diverged">
860
860
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
861
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
861
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
862
862
  <div class="compat-note">the app must use the same Firebase options as the host's other clients (one configuration per runtime)</div></div>
863
863
  </details>
864
864
  </div>
@@ -867,7 +867,7 @@ html{scrollbar-gutter:stable}
867
867
  <div class="compat-list">
868
868
  <details class="compat-row" data-status="diverged">
869
869
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
870
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
870
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
871
871
  <div class="compat-note">one SSR session per dev server process, as one Node server process has one auth state; per-request identity (<code>initializeServerApp</code>) is unsupported</div></div>
872
872
  </details>
873
873
  <details class="compat-row" data-status="diverged">
@@ -907,9 +907,9 @@ html{scrollbar-gutter:stable}
907
907
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><span class="compat-sub"><span class="compat-behavior">**Divergence:** <code>refFromURL(db, url)</code> matches Firebase's path parsing, query ignoring, FTP-to-database normalization, and fragment rejection, but the single-database sandbox does not validate the URL host/namespace against the handle; Firebase rejects a mismatched host while the sandbox uses its path.</span></span></span></summary>
908
908
  <div class="compat-evidence"><div class="compat-note">Oracle <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/rtdb-modular/rtdb-modular-reference-shape-url.json" target="_blank" rel="noopener noreferrer"><code>rtdb-modular-reference-shape-url</code></a> captures accepted FTP/query URLs and rejected fragments, malformed URLs, and host mismatches; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/modular/oracle-conformance-reference-writes.test.ts#L33" target="_blank" rel="noopener noreferrer"><code>unit:modular/oracle-conformance-reference-writes.test.ts</code></a> pins the conforming validations plus both sides of the host divergence.</div></div></details>
909
909
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
910
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
910
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
911
911
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
912
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
912
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
913
913
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">firebase-admin/database in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase-admin</code> in a server-rendered module resolves to <code>pyric-admin</code> bound to the dev server's host, as a <code>firebase-admin</code> process on the host is: a bare <code>initializeApp()</code> connects to it, reads bypass the rules, and the writes of browsers and SSR sessions are visible. On a SharedWorker dev server its first call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>, without dialing.</span></span></span></summary>
914
914
  <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> reads a path the rules let only the SSR user read, and a browser's write, with <code>firebase-admin</code> in SSR through both loaders and with each externalization setting, and gets the refusal on a SharedWorker dev server. On main (ba2c0bf0a) <code>firebase-admin</code> in SSR was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric-admin/test/app/ambient-init.test.ts#L128" target="_blank" rel="noopener noreferrer"><code>ambient-init.test.ts</code></a> pins that a bare <code>initializeApp()</code> binds the in-process activation's sandbox when PYRIC_SANDBOX is unset, and that the variable wins when set. CDD assertion <code>rtdb-modular#RS3</code> pins the sandbox side. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
915
915
  </div> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#status-legend" data-astro-cid-mw7aashj>Status legend</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#public-api" data-astro-cid-mw7aashj>Public API</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#getdatabasetarget--initializer" data-astro-cid-mw7aashj>getDatabase(target) — initializer</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#multiple-database-instances" data-astro-cid-mw7aashj>Multiple database instances</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#refdb-path--child--parent--root" data-astro-cid-mw7aashj>ref(db, path) / child / parent / root</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#getref--single-read" data-astro-cid-mw7aashj>get(ref) — single read</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#setref-value--full-write" data-astro-cid-mw7aashj>set(ref, value) — full write</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#updateref-values--partial--multi-path-update" data-astro-cid-mw7aashj>update(ref, values) — partial / multi-path update</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#removeref--delete" data-astro-cid-mw7aashj>remove(ref) — delete</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#pushref-value--auto-id-append" data-astro-cid-mw7aashj>push(ref, value?) — auto-id append</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#onvalueref-cb--value-level-listener" data-astro-cid-mw7aashj>onValue(ref, cb) — value-level listener</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#onchildadded--onchildchanged--onchildremoved--onchildmoved" data-astro-cid-mw7aashj>onChildAdded / onChildChanged / onChildRemoved / onChildMoved</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#offref-eventtype-callback--unsubscribe-variants" data-astro-cid-mw7aashj>off(ref, eventType?, callback?) — unsubscribe variants</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#queryref-constraints--ordering--bounds--limits" data-astro-cid-mw7aashj>query(ref, ...constraints) + ordering / bounds / limits</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#sentinels--servertimestamp--incrementn" data-astro-cid-mw7aashj>Sentinels — serverTimestamp() / increment(n)</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#runtransactionref-transactionupdate-options--optimistic-concurrency" data-astro-cid-mw7aashj>runTransaction(ref, transactionUpdate, options?) — optimistic concurrency</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#goonline--gooffline--connection-control" data-astro-cid-mw7aashj>goOnline / goOffline — connection control</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#transport-logging-and-url-reference-exports" data-astro-cid-mw7aashj>Transport, logging, and URL-reference exports</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#calls-in-flight-when-the-hosted-connection-drops" data-astro-cid-mw7aashj>Calls in flight when the hosted connection drops</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#client-sdks-in-a-registered-server-process" data-astro-cid-mw7aashj>Client SDKs in a registered server process</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#server-rendered-modules-under-vite-dev" data-astro-cid-mw7aashj>Server-rendered modules under vite dev</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#current-gaps" data-astro-cid-mw7aashj>Current gaps</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#documented-divergences" data-astro-cid-mw7aashj>Documented divergences</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
@@ -901,7 +901,7 @@ A server process started with `node --import @pyric/cli/register` and `PYRIC_SAN
901
901
  <div class="compat-list">
902
902
  <details class="compat-row" data-status="diverged">
903
903
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
904
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
904
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
905
905
  <div class="compat-note">the app must use the same Firebase options as the host's other clients (one configuration per runtime)</div></div>
906
906
  </details>
907
907
  </div>
@@ -913,7 +913,7 @@ Modules Vite loads for server rendering under `vite dev` with `pyric()` (`server
913
913
  <div class="compat-list">
914
914
  <details class="compat-row" data-status="diverged">
915
915
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
916
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
916
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
917
917
  <div class="compat-note">one SSR session per dev server process, as one Node server process has one auth state; per-request identity (<code>initializeServerApp</code>) is unsupported</div></div>
918
918
  </details>
919
919
  <details class="compat-row" data-status="diverged">
@@ -957,9 +957,9 @@ Known differences between Pyric and production Firebase. Each remains tracked as
957
957
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><span class="compat-sub"><span class="compat-behavior">**Divergence:** <code>refFromURL(db, url)</code> matches Firebase's path parsing, query ignoring, FTP-to-database normalization, and fragment rejection, but the single-database sandbox does not validate the URL host/namespace against the handle; Firebase rejects a mismatched host while the sandbox uses its path.</span></span></span></summary>
958
958
  <div class="compat-evidence"><div class="compat-note">Oracle <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/rtdb-modular/rtdb-modular-reference-shape-url.json" target="_blank" rel="noopener noreferrer"><code>rtdb-modular-reference-shape-url</code></a> captures accepted FTP/query URLs and rejected fragments, malformed URLs, and host mismatches; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/modular/oracle-conformance-reference-writes.test.ts#L33" target="_blank" rel="noopener noreferrer"><code>unit:modular/oracle-conformance-reference-writes.test.ts</code></a> pins the conforming validations plus both sides of the host divergence.</div></div></details>
959
959
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
960
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
960
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
961
961
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
962
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
962
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
963
963
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">firebase-admin/database in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase-admin</code> in a server-rendered module resolves to <code>pyric-admin</code> bound to the dev server's host, as a <code>firebase-admin</code> process on the host is: a bare <code>initializeApp()</code> connects to it, reads bypass the rules, and the writes of browsers and SSR sessions are visible. On a SharedWorker dev server its first call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>, without dialing.</span></span></span></summary>
964
964
  <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> reads a path the rules let only the SSR user read, and a browser's write, with <code>firebase-admin</code> in SSR through both loaders and with each externalization setting, and gets the refusal on a SharedWorker dev server. On main (ba2c0bf0a) <code>firebase-admin</code> in SSR was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric-admin/test/app/ambient-init.test.ts#L128" target="_blank" rel="noopener noreferrer"><code>ambient-init.test.ts</code></a> pins that a bare <code>initializeApp()</code> binds the in-process activation's sandbox when PYRIC_SANDBOX is unset, and that the variable wins when set. CDD assertion <code>rtdb-modular#RS3</code> pins the sandbox side. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
965
965
  </div>