@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (301) hide show
  1. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  2. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  4. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  5. package/dist/conformance/.generated/can-i-use.js +2 -2
  6. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  7. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  8. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  9. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  10. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  11. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  12. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  13. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  14. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  19. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  20. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  23. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  24. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/index.json +200 -25
  30. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  35. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  36. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  37. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  38. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  39. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  40. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  41. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  42. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  50. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  51. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +131 -66
  57. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
  58. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  68. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  69. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  71. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  72. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  80. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  81. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  99. package/dist/serve/docs-ui/llms.txt +1 -1
  100. package/dist/serve/worker/host/core.js +1 -1
  101. package/dist/serve/worker/host/core.js.map +1 -1
  102. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  103. package/dist/serve/worker/host/firestore-writes.js +3 -2
  104. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  105. package/dist/serve/worker/index.d.ts +2 -1
  106. package/dist/serve/worker/index.d.ts.map +1 -1
  107. package/dist/serve/worker/index.js +2 -1
  108. package/dist/serve/worker/index.js.map +1 -1
  109. package/dist/serve/worker/protocol.js +2 -2
  110. package/dist/serve/worker/protocol.js.map +1 -1
  111. package/package.json +5 -4
  112. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  113. package/src/assurance/attachment.ts +211 -0
  114. package/src/assurance/browser.ts +80 -0
  115. package/src/assurance/campaign.ts +496 -0
  116. package/src/assurance/capabilities.ts +446 -0
  117. package/src/assurance/cases.ts +43 -0
  118. package/src/assurance/index.ts +75 -0
  119. package/src/assurance/runner.ts +885 -0
  120. package/src/assurance/tool-names.ts +15 -0
  121. package/src/assurance/tools.ts +830 -0
  122. package/src/assurance/types.ts +352 -0
  123. package/src/assurance/validation.ts +434 -0
  124. package/src/bridge/client/bridge.ts +550 -0
  125. package/src/bridge/client/dispatch.ts +137 -0
  126. package/src/bridge/client.ts +45 -0
  127. package/src/bridge/protocol.ts +351 -0
  128. package/src/bridge/server/audit.ts +53 -0
  129. package/src/bridge/server/bridge.ts +597 -0
  130. package/src/bridge/server/headless.ts +160 -0
  131. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  132. package/src/bridge/server/local-bridge.ts +66 -0
  133. package/src/bridge/server/logger.ts +50 -0
  134. package/src/bridge/server/mcp-contract.ts +93 -0
  135. package/src/bridge/server/mcp.ts +129 -0
  136. package/src/bridge/server/peer.ts +232 -0
  137. package/src/bridge/server/standalone.ts +347 -0
  138. package/src/bridge/server/tool-metadata.ts +99 -0
  139. package/src/bridge/server.ts +28 -0
  140. package/src/cli/can-i-use.ts +50 -0
  141. package/src/cli/cli.test.ts +493 -0
  142. package/src/cli/database-rules.ts +329 -0
  143. package/src/cli/dev-runner.ts +282 -0
  144. package/src/cli/firebase-json.ts +100 -0
  145. package/src/cli/firestore-indexes.ts +70 -0
  146. package/src/cli/index.ts +425 -0
  147. package/src/cli/init.ts +260 -0
  148. package/src/cli/mcp-proxy.ts +196 -0
  149. package/src/cli/parse-args.ts +80 -0
  150. package/src/cli/rules.ts +259 -0
  151. package/src/cli/scope.ts +67 -0
  152. package/src/cli/serve.ts +996 -0
  153. package/src/cli/service-commands.ts +73 -0
  154. package/src/cli/snapshot.ts +164 -0
  155. package/src/cli/storage-rules.ts +172 -0
  156. package/src/cli/verify.ts +416 -0
  157. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  158. package/src/conformance/.generated/can-i-use.ts +117 -0
  159. package/src/conformance/.generated/conformance-docs.ts +14 -0
  160. package/src/conformance/browser.ts +24 -0
  161. package/src/conformance/can-i-use-tool.ts +51 -0
  162. package/src/conformance/can-i-use.ts +18 -0
  163. package/src/conformance/docs.ts +8 -0
  164. package/src/conformance/index.ts +20 -0
  165. package/src/conformance/tools.ts +16 -0
  166. package/src/credentials/core/memoize-ttl.ts +142 -0
  167. package/src/credentials/core/types.ts +10 -0
  168. package/src/credentials/node/from-adc.ts +96 -0
  169. package/src/credentials/node/from-service-account.ts +133 -0
  170. package/src/credentials/node/index.ts +6 -0
  171. package/src/discover/concurrency.ts +127 -0
  172. package/src/discover/crawler-adapter.ts +142 -0
  173. package/src/discover/crawler.ts +1127 -0
  174. package/src/discover/credential-free.ts +27 -0
  175. package/src/discover/findCollectionGroup.ts +131 -0
  176. package/src/discover/firestore-source.ts +59 -0
  177. package/src/discover/index.ts +5 -0
  178. package/src/discover/merge.ts +523 -0
  179. package/src/discover/session.ts +402 -0
  180. package/src/discover/tools.ts +201 -0
  181. package/src/discover/types.ts +187 -0
  182. package/src/discover/wire.ts +324 -0
  183. package/src/functions-rtdb/child.ts +382 -0
  184. package/src/functions-rtdb/delivery.ts +7 -0
  185. package/src/functions-rtdb/discovery.ts +136 -0
  186. package/src/functions-rtdb/event.ts +64 -0
  187. package/src/functions-rtdb/execution.ts +111 -0
  188. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  189. package/src/functions-rtdb/project.ts +74 -0
  190. package/src/functions-rtdb/projection.ts +104 -0
  191. package/src/functions-rtdb/reference-pattern.ts +20 -0
  192. package/src/functions-rtdb/remote-delivery.ts +23 -0
  193. package/src/pkg-version.ts +49 -0
  194. package/src/register/esm-exports.ts +63 -0
  195. package/src/register/hooks.ts +30 -0
  196. package/src/register/index.ts +150 -0
  197. package/src/register/mapping.ts +33 -0
  198. package/src/remote/index.ts +1050 -0
  199. package/src/rtdb/crawl-snapshot.ts +118 -0
  200. package/src/rtdb/inspection.ts +137 -0
  201. package/src/rtdb/load-rules-document.ts +56 -0
  202. package/src/rtdb/rules-generation-tool.ts +38 -0
  203. package/src/rtdb/rules-json.ts +29 -0
  204. package/src/serve/activity-guard.ts +29 -0
  205. package/src/serve/activity-route.ts +164 -0
  206. package/src/serve/activity-warning.ts +26 -0
  207. package/src/serve/bridge-mount.ts +200 -0
  208. package/src/serve/bundler.ts +493 -0
  209. package/src/serve/capture-store.ts +65 -0
  210. package/src/serve/discovery.ts +199 -0
  211. package/src/serve/entries/ai.ts +292 -0
  212. package/src/serve/entries/app-backend.ts +4 -0
  213. package/src/serve/entries/app-client.ts +26 -0
  214. package/src/serve/entries/app-session-store.ts +47 -0
  215. package/src/serve/entries/app.ts +11 -0
  216. package/src/serve/entries/auth-helper-core.ts +181 -0
  217. package/src/serve/entries/auth-helper-dom.ts +134 -0
  218. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  219. package/src/serve/entries/auth.ts +255 -0
  220. package/src/serve/entries/bridge-url.ts +31 -0
  221. package/src/serve/entries/database.ts +129 -0
  222. package/src/serve/entries/firestore.ts +232 -0
  223. package/src/serve/entries/init.ts +46 -0
  224. package/src/serve/entries/keepalive.ts +46 -0
  225. package/src/serve/entries/messaging-sw.ts +75 -0
  226. package/src/serve/entries/messaging.ts +96 -0
  227. package/src/serve/entries/runtime.ts +540 -0
  228. package/src/serve/entries/session-store.ts +97 -0
  229. package/src/serve/entries/storage.ts +92 -0
  230. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  231. package/src/serve/entries/worker-runtime.ts +75 -0
  232. package/src/serve/init-payload.ts +42 -0
  233. package/src/serve/namespace.ts +675 -0
  234. package/src/serve/open-browser.ts +68 -0
  235. package/src/serve/rules.ts +272 -0
  236. package/src/serve/sandbox-marker.ts +31 -0
  237. package/src/serve/server.ts +428 -0
  238. package/src/serve/standalone-assets.ts +180 -0
  239. package/src/serve/state-store.ts +166 -0
  240. package/src/serve/studio/disk-project-store.ts +185 -0
  241. package/src/serve/studio/disk-workspace.ts +162 -0
  242. package/src/serve/studio/index.ts +20 -0
  243. package/src/serve/studio/routes.ts +237 -0
  244. package/src/serve/studio/store-types.ts +48 -0
  245. package/src/serve/studio/studio-storage.test.ts +289 -0
  246. package/src/serve/vite-plugin.ts +1102 -0
  247. package/src/serve/worker/activity-bootstrap.ts +24 -0
  248. package/src/serve/worker/client/admin-firestore.ts +38 -0
  249. package/src/serve/worker/client/ai.ts +124 -0
  250. package/src/serve/worker/client/auth.ts +489 -0
  251. package/src/serve/worker/client/connection.ts +202 -0
  252. package/src/serve/worker/client/core.ts +350 -0
  253. package/src/serve/worker/client/disconnect.ts +45 -0
  254. package/src/serve/worker/client/firestore-reads.ts +181 -0
  255. package/src/serve/worker/client/firestore-refs.ts +226 -0
  256. package/src/serve/worker/client/firestore-writes.ts +232 -0
  257. package/src/serve/worker/client/handles.ts +91 -0
  258. package/src/serve/worker/client/messaging.ts +119 -0
  259. package/src/serve/worker/client/presence.ts +174 -0
  260. package/src/serve/worker/client/rtdb.ts +311 -0
  261. package/src/serve/worker/client/rules.ts +63 -0
  262. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  263. package/src/serve/worker/client/snapshots.ts +86 -0
  264. package/src/serve/worker/client/storage.ts +195 -0
  265. package/src/serve/worker/client/studio.ts +88 -0
  266. package/src/serve/worker/client.ts +57 -0
  267. package/src/serve/worker/durable-persistence.ts +137 -0
  268. package/src/serve/worker/entry.ts +179 -0
  269. package/src/serve/worker/host/admin-firestore.ts +88 -0
  270. package/src/serve/worker/host/connection.ts +179 -0
  271. package/src/serve/worker/host/core.ts +387 -0
  272. package/src/serve/worker/host/dispatch.ts +316 -0
  273. package/src/serve/worker/host/firestore-reads.ts +117 -0
  274. package/src/serve/worker/host/firestore-writes.ts +456 -0
  275. package/src/serve/worker/host/presence.ts +312 -0
  276. package/src/serve/worker/host/rtdb.ts +136 -0
  277. package/src/serve/worker/host/rules.ts +128 -0
  278. package/src/serve/worker/host/storage.ts +312 -0
  279. package/src/serve/worker/host/studio.ts +75 -0
  280. package/src/serve/worker/host/subscriptions.ts +238 -0
  281. package/src/serve/worker/host-ai.ts +165 -0
  282. package/src/serve/worker/host-auth.ts +469 -0
  283. package/src/serve/worker/host-context.ts +273 -0
  284. package/src/serve/worker/host-events.ts +94 -0
  285. package/src/serve/worker/host-messaging.ts +240 -0
  286. package/src/serve/worker/host.ts +47 -0
  287. package/src/serve/worker/index.ts +196 -0
  288. package/src/serve/worker/presence-timing.ts +15 -0
  289. package/src/serve/worker/protocol.ts +1112 -0
  290. package/src/serve/worker/serve-init.ts +606 -0
  291. package/src/serve/worker/service-worker-channel.ts +33 -0
  292. package/src/serve/worker/service-worker-relay.ts +86 -0
  293. package/src/serve/writer-lock.ts +53 -0
  294. package/src/verify/cases.ts +233 -0
  295. package/src/verify/fixture.ts +258 -0
  296. package/src/verify/index.ts +519 -0
  297. package/src/verify/tools.ts +108 -0
  298. package/src/version/compat-target.ts +17 -0
  299. package/src/vite.ts +17 -0
  300. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  301. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,24 @@
1
+ import type { ActivityMonitor } from 'pyric/firestore/internal';
2
+ import { setupFirebaseActivityGuard as setupActivityGuardForFeed } from '../activity-guard.js';
3
+ import type { HostCtx } from './host.js';
4
+
5
+ export interface WorkerActivityEnv {
6
+ fetch: typeof fetch;
7
+ }
8
+
9
+ /** Own the worker-lifetime warning guard after capture history has hydrated. */
10
+ export function setupFirebaseActivityGuard(
11
+ ctx: HostCtx,
12
+ env: WorkerActivityEnv,
13
+ activityToken: string | undefined,
14
+ ): ActivityMonitor | null {
15
+ if (!activityToken) return null;
16
+ return setupActivityGuardForFeed(
17
+ {
18
+ history: () => ctx.sandbox.history(),
19
+ subscribe: (listener) => ctx.sandbox.onEvent(listener),
20
+ },
21
+ env.fetch,
22
+ activityToken,
23
+ );
24
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Admin-lens Firestore document ops over the worker port (Pyric Studio data
3
+ * browse) — rule-bypassing reads/writes the host resolves under the admin lens.
4
+ */
5
+
6
+ import { nextId, rpc } from './core.js';
7
+ import type { ClientDb } from './handles.js';
8
+
9
+ export async function adminGetDocument(db: ClientDb, path: string): Promise<Record<string, unknown> | null> {
10
+ return (await rpc(db.port, { t: 'op', id: nextId(), method: 'admin.getDocument', path })) as Record<string, unknown> | null;
11
+ }
12
+
13
+ export async function adminListDocuments(
14
+ db: ClientDb,
15
+ path: string,
16
+ ): Promise<Array<{ path: string; data: unknown; phantom?: boolean }>> {
17
+ return (await rpc(db.port, { t: 'op', id: nextId(), method: 'admin.listDocuments', path })) as Array<{ path: string; data: unknown; phantom?: boolean }>;
18
+ }
19
+
20
+ export async function adminSetDocument(db: ClientDb, path: string, data: unknown): Promise<void> {
21
+ await rpc(db.port, { t: 'op', id: nextId(), method: 'admin.setDocument', path, data });
22
+ }
23
+
24
+ export async function adminDeleteDocument(db: ClientDb, path: string): Promise<boolean> {
25
+ return (await rpc(db.port, { t: 'op', id: nextId(), method: 'admin.deleteDocument', path })) as boolean;
26
+ }
27
+
28
+ export async function adminReadState(
29
+ db: ClientDb,
30
+ opts: { path?: string; maxDepth?: number } = {},
31
+ ): Promise<Record<string, unknown>> {
32
+ return (await rpc(db.port, {
33
+ t: 'op',
34
+ id: nextId(),
35
+ method: 'admin.readState',
36
+ ...opts,
37
+ })) as Record<string, unknown>;
38
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * AI ops over the worker port — the served firebase/ai entry plugs these in
3
+ * as the mirror AnswerEngine on the worker path (see entries/ai.ts).
4
+ */
5
+
6
+ import type { AiEngineConfigWire, InboundMessage } from '../protocol.js';
7
+ import { closeSubscription, nextId, nextSubId, openSnapshotSubscription, rpc } from './core.js';
8
+ import type { ClientDb } from './handles.js';
9
+
10
+ // ─── AI ops (pyric/ai over the worker — cdd-deltas #98.3) ──────────────────
11
+ //
12
+ // The served `firebase/ai` entry (entries/ai.ts) plugs these three functions
13
+ // in as the mirror's AnswerEngine on the worker path, so the page keeps the
14
+ // full `pyric/ai` surface (GenerativeModel / ChatSession / Schema) while
15
+ // ANSWERING happens in the worker host's broker — in-process with the ONE
16
+ // shared sandbox (#98.1), whose unified event stream Studio consumes.
17
+ // Requests/responses are the plain Gemini-wire JSON the broker speaks; no
18
+ // codec round-trip. `engine` rides each message but is honored only on the
19
+ // worker's FIRST ai op (broker creation — see AiEngineConfigWire).
20
+
21
+ /** Parameters shared by the three AI ops. */
22
+ export interface AiOpParams {
23
+ /** Model resource the mirror resolved (e.g. `models/gemini-flash-lite-latest`). */
24
+ model: string;
25
+ /** Plain Gemini-wire request JSON. */
26
+ request: Record<string, unknown>;
27
+ /** First-op engine config — see {@link AiEngineConfigWire}. */
28
+ engine?: AiEngineConfigWire;
29
+ }
30
+
31
+ /** Unary `generateContent` against the worker's broker. Resolves with the
32
+ * complete WireResponse envelope; rejects with an Error carrying `.code`
33
+ * (and `.aiEnvelope` when the broker answered a wire error envelope). */
34
+ export async function aiGenerateContent(
35
+ db: ClientDb,
36
+ params: AiOpParams,
37
+ ): Promise<Record<string, unknown>> {
38
+ return (await rpc(db.port, {
39
+ t: 'op',
40
+ id: nextId(),
41
+ method: 'ai.generateContent',
42
+ model: params.model,
43
+ request: params.request,
44
+ ...(params.engine !== undefined ? { engine: params.engine } : {}),
45
+ })) as Record<string, unknown>;
46
+ }
47
+
48
+ /** Unary `countTokens` against the worker's broker (deterministic). */
49
+ export async function aiCountTokens(
50
+ db: ClientDb,
51
+ params: AiOpParams,
52
+ ): Promise<Record<string, unknown>> {
53
+ return (await rpc(db.port, {
54
+ t: 'op',
55
+ id: nextId(),
56
+ method: 'ai.countTokens',
57
+ model: params.model,
58
+ request: params.request,
59
+ ...(params.engine !== undefined ? { engine: params.engine } : {}),
60
+ })) as Record<string, unknown>;
61
+ }
62
+
63
+ /**
64
+ * Streamed `generateContent` over an AI stream SUBSCRIPTION: chunks arrive
65
+ * as `{ chunk }` snaps in order, the terminal `{ done: true }` snap ends the
66
+ * iteration, and a snap `__error` throws (with `.code` / `.aiEnvelope`).
67
+ * The host auto-unsubs on done; the generator additionally sends `unsub` on
68
+ * teardown so early consumer abandonment cancels the worker-side pump (the
69
+ * done/unsub race is a benign no-op on the host).
70
+ */
71
+ export async function* aiStreamGenerateContent(
72
+ db: ClientDb,
73
+ params: AiOpParams,
74
+ ): AsyncGenerator<Record<string, unknown>> {
75
+ const subId = nextSubId();
76
+ type QueueItem =
77
+ | { kind: 'chunk'; chunk: Record<string, unknown> }
78
+ | { kind: 'done' }
79
+ | { kind: 'error'; error: unknown };
80
+ const queue: QueueItem[] = [];
81
+ let notify: (() => void) | null = null;
82
+ const push = (item: QueueItem): void => {
83
+ queue.push(item);
84
+ notify?.();
85
+ notify = null;
86
+ };
87
+
88
+ const opened = openSnapshotSubscription(db.port, subId, {
89
+ port: db.port,
90
+ next: (raw) => {
91
+ const v = (raw ?? {}) as { chunk?: Record<string, unknown>; done?: boolean };
92
+ if (v.done) push({ kind: 'done' });
93
+ else push({ kind: 'chunk', chunk: v.chunk ?? {} });
94
+ },
95
+ error: (err) => push({ kind: 'error', error: err }),
96
+ }, {
97
+ t: 'sub',
98
+ subId,
99
+ target: { service: 'ai', op: 'streamGenerateContent' },
100
+ model: params.model,
101
+ request: params.request,
102
+ ...(params.engine !== undefined ? { engine: params.engine } : {}),
103
+ } satisfies InboundMessage);
104
+ if (!opened) push({
105
+ kind: 'error',
106
+ error: Object.assign(new Error('Firebase App was deleted'), { code: 'app/app-deleted' }),
107
+ });
108
+
109
+ try {
110
+ for (;;) {
111
+ while (queue.length === 0) {
112
+ await new Promise<void>((resolve) => {
113
+ notify = resolve;
114
+ });
115
+ }
116
+ const item = queue.shift()!;
117
+ if (item.kind === 'error') throw item.error;
118
+ if (item.kind === 'done') return;
119
+ yield item.chunk;
120
+ }
121
+ } finally {
122
+ closeSubscription(db.port, subId);
123
+ }
124
+ }
@@ -0,0 +1,489 @@
1
+ /**
2
+ * Worker-client Auth surface — mirrors `pyric/auth` / `firebase/auth` over the
3
+ * port. Per-port sessions (#754): each port owns its own session; the client
4
+ * keeps a local `currentUser` mirror updated from the authState stream while the
5
+ * worker owns the real `User` and token minting.
6
+ */
7
+
8
+ import type {
9
+ InboundMessage,
10
+ SerializedUser,
11
+ SerializedUserCredential,
12
+ SerializedIdTokenResult,
13
+ AuthPersistenceMode,
14
+ ResolvedIdentity,
15
+ } from '../protocol.js';
16
+ import type { AuthUserRecord, CreateUserRequest, UpdateUserRequest } from 'pyric/auth';
17
+ import { closeSubscription, isDisconnectedPort, nextId, nextSubId, openSnapshotSubscription, rpc, wirePort } from './core.js';
18
+ import type { ClientDb, ClientPort, Unsubscribe } from './handles.js';
19
+
20
+ // ════════════════════════════════════════════════════════════════════════
21
+ // AUTH SURFACE (mirrors `pyric/auth` / `firebase/auth`)
22
+ // ════════════════════════════════════════════════════════════════════════
23
+ //
24
+ // PER-PORT SESSIONS (#754)
25
+ // ------------------------
26
+ // The worker hosts ONE sandbox (one user pool, one data store, one ruleset)
27
+ // but each port (tab / client) owns its OWN session: sign-ins bind to THIS
28
+ // port, `onAuthStateChanged` fires for THIS port's transitions only, and
29
+ // data ops from this port evaluate rules under its session. Two tabs can be
30
+ // two different users on the same live backend — the multi-user testing
31
+ // surface a single-identity sandbox can't provide. Session persistence
32
+ // across reloads is client-side (SessionStore + `restorePortSession`).
33
+ //
34
+ // CLIENT-SIDE currentUser MIRROR
35
+ // ------------------------------
36
+ // The worker holds the one real `User`. The client keeps a local
37
+ // `ClientUser` mirror updated from the authState stream, so `auth.currentUser`
38
+ // is synchronously readable (matching firebase/auth). Token accessors RPC to
39
+ // the worker (the worker owns token minting).
40
+
41
+ /**
42
+ * Client-side User — a snapshot of the worker's `User` with token accessors
43
+ * that RPC back to the worker. Mirrors `firebase/auth`'s `User` shape.
44
+ */
45
+ export interface ClientUser {
46
+ readonly uid: string;
47
+ readonly email: string | null;
48
+ readonly emailVerified: boolean;
49
+ readonly displayName: string | null;
50
+ readonly photoURL: string | null;
51
+ readonly phoneNumber: string | null;
52
+ readonly isAnonymous: boolean;
53
+ readonly providerId: string | null;
54
+ readonly providerData: SerializedUser['providerData'];
55
+ getIdToken(forceRefresh?: boolean): Promise<string>;
56
+ getIdTokenResult(forceRefresh?: boolean): Promise<SerializedIdTokenResult>;
57
+ }
58
+
59
+ /** Client-side UserCredential — mirrors `firebase/auth`. */
60
+ export interface ClientUserCredential {
61
+ user: ClientUser;
62
+ providerId: string | null;
63
+ operationType: 'signIn' | 'reauthenticate' | 'link';
64
+ }
65
+
66
+ /**
67
+ * Client-side Auth handle. Holds the port + a local `currentUser` mirror.
68
+ * Returned by `getAuth(db | workerUrl)`. Mirrors `firebase/auth`'s `Auth`.
69
+ */
70
+ export interface ClientAuth {
71
+ readonly __kind: 'client-auth';
72
+ readonly port: ClientPort;
73
+ /** Local mirror of the worker's currentUser, updated from the stream. */
74
+ currentUser: ClientUser | null;
75
+ }
76
+
77
+ /** Hidden per-`ClientUser` port handle so the top-level `updateProfile(user, …)`
78
+ * free function can RPC without an `auth` handle in scope (mirrors
79
+ * `firebase/auth`'s user-only signature). Non-enumerable — never serialized. */
80
+ const CLIENT_USER_PORT: unique symbol = Symbol('pyric.clientUser.port');
81
+
82
+ /** Build a token-capable ClientUser from a wire SerializedUser. */
83
+ function makeClientUser(port: ClientPort, raw: SerializedUser): ClientUser {
84
+ const user: ClientUser = {
85
+ uid: raw.uid,
86
+ email: raw.email,
87
+ emailVerified: raw.emailVerified,
88
+ displayName: raw.displayName,
89
+ photoURL: raw.photoURL,
90
+ phoneNumber: raw.phoneNumber,
91
+ isAnonymous: raw.isAnonymous,
92
+ providerId: raw.providerId,
93
+ providerData: raw.providerData,
94
+ async getIdToken(forceRefresh?: boolean) {
95
+ return (await rpc(port, {
96
+ t: 'op', id: nextId(), method: 'auth.getIdToken', forceRefresh,
97
+ })) as string;
98
+ },
99
+ async getIdTokenResult(forceRefresh?: boolean) {
100
+ return (await rpc(port, {
101
+ t: 'op', id: nextId(), method: 'auth.getIdTokenResult', forceRefresh,
102
+ })) as SerializedIdTokenResult;
103
+ },
104
+ };
105
+ Object.defineProperty(user, CLIENT_USER_PORT, { value: port, enumerable: false });
106
+ return user;
107
+ }
108
+
109
+ /** Convert a wire SerializedUser|null to a ClientUser|null. */
110
+ function toClientUser(port: ClientPort, raw: SerializedUser | null): ClientUser | null {
111
+ return raw ? makeClientUser(port, raw) : null;
112
+ }
113
+
114
+ /**
115
+ * Get the worker-backed Auth handle.
116
+ *
117
+ * Mirrors `pyric/auth`'s `getAuth(sandbox)` / `firebase/auth`'s
118
+ * `getAuth(app)` — but the input is either an existing `ClientDb` (reusing
119
+ * its port, the common case in serve where Firestore + auth share one
120
+ * worker) or a worker URL (standalone).
121
+ *
122
+ * The returned handle seeds its `currentUser` mirror by opening an internal
123
+ * authState subscription that keeps it live across tabs.
124
+ */
125
+ export function getAuth(source: ClientDb | string | URL, name?: string): ClientAuth {
126
+ let port: ClientPort;
127
+ if (typeof source === 'object' && '__kind' in source && source.__kind === 'client-db') {
128
+ port = source.port;
129
+ } else {
130
+ if (typeof SharedWorker === 'undefined') {
131
+ throw new Error(
132
+ 'SharedWorker is not available. ' +
133
+ 'Open this page over http:// (not file://) and use a supported browser ' +
134
+ '(Chrome 4+, Firefox 29+, Safari 16.4+).',
135
+ );
136
+ }
137
+ const worker = new SharedWorker(source as string | URL, {
138
+ type: 'classic',
139
+ name: name ?? 'pyric-shared-worker',
140
+ });
141
+ port = worker.port;
142
+ port.start();
143
+ wirePort(port);
144
+ }
145
+
146
+ const auth: ClientAuth = { __kind: 'client-auth', port, currentUser: null };
147
+
148
+ // Internal authState subscription keeps `auth.currentUser` live. Per-port
149
+ // sessions (#754): only THIS port's sign-ins/outs (and its session restore)
150
+ // fire here — another tab's sign-in is another user, not an update to us.
151
+ const subId = nextSubId();
152
+ openSnapshotSubscription(port, subId, {
153
+ port,
154
+ next: (raw) => {
155
+ auth.currentUser = toClientUser(port, raw as SerializedUser | null);
156
+ },
157
+ }, { t: 'sub', subId, target: 'authState' } satisfies InboundMessage);
158
+
159
+ return auth;
160
+ }
161
+
162
+ /**
163
+ * Connect to the auth emulator. No-op shim over the worker: the worker's
164
+ * sandbox IS the emulator-equivalent backend, so there's nothing to point at.
165
+ * Present for surface parity so app code that calls it doesn't break.
166
+ */
167
+ export function connectAuthEmulator(
168
+ _auth: ClientAuth,
169
+ _url: string,
170
+ _options?: { disableWarnings?: boolean },
171
+ ): void {
172
+ // Intentional no-op — the worker's sandbox is the local auth backend.
173
+ }
174
+
175
+ // ─── Sign-in / out / create (RPC) ─────────────────────────────────────────
176
+
177
+ export async function createUserWithEmailAndPassword(
178
+ auth: ClientAuth,
179
+ email: string,
180
+ password: string,
181
+ ): Promise<ClientUserCredential> {
182
+ const raw = (await rpc(auth.port, {
183
+ t: 'op', id: nextId(), method: 'auth.createUser', email, password,
184
+ })) as SerializedUserCredential;
185
+ return hydrateCred(auth, raw);
186
+ }
187
+
188
+ // ─── Admin user-DB ops (Pyric Studio data browse) ─────────────────────────
189
+ // Mirror `pyric/auth`'s `sandbox.{listUsers,createUser,updateUser,deleteUser,
190
+ // clearUsers}` over the port. No lens (admin control surface), so bare `rpc`.
191
+
192
+ export async function listUsers(auth: ClientAuth): Promise<AuthUserRecord[]> {
193
+ return (await rpc(auth.port, {
194
+ t: 'op', id: nextId(), method: 'auth.listUsers',
195
+ })) as AuthUserRecord[];
196
+ }
197
+
198
+ export async function adminCreateUser(
199
+ auth: ClientAuth,
200
+ request: CreateUserRequest,
201
+ ): Promise<AuthUserRecord> {
202
+ return (await rpc(auth.port, {
203
+ t: 'op', id: nextId(), method: 'auth.adminCreateUser',
204
+ request: request as unknown as Record<string, unknown>,
205
+ })) as AuthUserRecord;
206
+ }
207
+
208
+ export async function adminUpdateUser(
209
+ auth: ClientAuth,
210
+ uid: string,
211
+ request: UpdateUserRequest,
212
+ ): Promise<AuthUserRecord> {
213
+ return (await rpc(auth.port, {
214
+ t: 'op', id: nextId(), method: 'auth.adminUpdateUser', uid,
215
+ request: request as unknown as Record<string, unknown>,
216
+ })) as AuthUserRecord;
217
+ }
218
+
219
+ export async function adminDeleteUser(auth: ClientAuth, uid: string): Promise<void> {
220
+ await rpc(auth.port, { t: 'op', id: nextId(), method: 'auth.adminDeleteUser', uid });
221
+ }
222
+
223
+ export async function adminClearUsers(auth: ClientAuth): Promise<void> {
224
+ await rpc(auth.port, { t: 'op', id: nextId(), method: 'auth.adminClearUsers' });
225
+ }
226
+
227
+ // ─── Sign-in provider config (Pyric Studio S-AUTH) ────────────────────────
228
+ // Mirror `pyric/auth`'s `sandbox.{getAuthProviderConfig,setAuthProviderConfig}`
229
+ // over the port. No dedicated subscription: `setProviderConfig` fires a
230
+ // `provider_config_update` sandbox event, so a caller re-reads via
231
+ // `getProviderConfig` on the SAME event feed `listUsers` callers already
232
+ // subscribe to (see `worker-live.ts`'s `subscribeUsers`).
233
+
234
+ export async function getProviderConfig(
235
+ auth: ClientAuth,
236
+ ): Promise<Array<{ providerId: string; enabled: boolean }>> {
237
+ return (await rpc(auth.port, {
238
+ t: 'op', id: nextId(), method: 'auth.getProviderConfig',
239
+ })) as Array<{ providerId: string; enabled: boolean }>;
240
+ }
241
+
242
+ export async function setProviderConfig(
243
+ auth: ClientAuth,
244
+ providerId: string,
245
+ enabled: boolean,
246
+ ): Promise<void> {
247
+ await rpc(auth.port, {
248
+ t: 'op', id: nextId(), method: 'auth.setProviderConfig', providerId, enabled,
249
+ });
250
+ }
251
+
252
+ export async function signInWithEmailAndPassword(
253
+ auth: ClientAuth,
254
+ email: string,
255
+ password: string,
256
+ ): Promise<ClientUserCredential> {
257
+ const raw = (await rpc(auth.port, {
258
+ t: 'op', id: nextId(), method: 'auth.signInEmail', email, password,
259
+ })) as SerializedUserCredential;
260
+ return hydrateCred(auth, raw);
261
+ }
262
+
263
+ export async function signInAnonymously(auth: ClientAuth): Promise<ClientUserCredential> {
264
+ const raw = (await rpc(auth.port, {
265
+ t: 'op', id: nextId(), method: 'auth.signInAnonymously',
266
+ })) as SerializedUserCredential;
267
+ return hydrateCred(auth, raw);
268
+ }
269
+
270
+ export async function signOut(auth: ClientAuth): Promise<void> {
271
+ if (isDisconnectedPort(auth.port)) {
272
+ auth.currentUser = null;
273
+ return;
274
+ }
275
+ await rpc(auth.port, { t: 'op', id: nextId(), method: 'auth.signOut' });
276
+ // The authState stream will also clear the mirror; set eagerly so a
277
+ // synchronous read right after `await signOut()` reflects the change.
278
+ auth.currentUser = null;
279
+ }
280
+
281
+ /**
282
+ * Bridge a provider identity resolved IN-PAGE to the worker (the provider
283
+ * sign-in seam). The entry adapter's worker-path `signInWithPopup`/
284
+ * `signInWithRedirect` runs the in-page `AuthFlowResolver` (which can't cross
285
+ * the worker port), then calls this with the picked identity; the worker seeds
286
+ * it + signs it in, returning a worker-backed credential. The mirror updates
287
+ * eagerly (like the email/anon paths) so a synchronous `auth.currentUser`
288
+ * read right after the await reflects the new user.
289
+ */
290
+ export async function acceptProviderCredential(
291
+ auth: ClientAuth,
292
+ identity: ResolvedIdentity,
293
+ ): Promise<ClientUserCredential> {
294
+ const raw = (await rpc(auth.port, {
295
+ t: 'op', id: nextId(), method: 'auth.acceptIdentity', identity,
296
+ })) as SerializedUserCredential;
297
+ return hydrateCred(auth, raw);
298
+ }
299
+
300
+ /**
301
+ * Re-establish THIS PORT's session for an existing identity (#754) — the
302
+ * per-tab reload restore. The page persists its uid in web storage
303
+ * (SessionStore); runtime.ts calls this at boot BEFORE app code runs. Soft:
304
+ * returns null (and leaves the port signed out) when the uid no longer
305
+ * resolves, so a stale record never throws.
306
+ */
307
+ export async function restorePortSession(
308
+ auth: ClientAuth,
309
+ uid: string,
310
+ ): Promise<ClientUser | null> {
311
+ const raw = (await rpc(auth.port, {
312
+ t: 'op', id: nextId(), method: 'auth.restorePortSession', uid,
313
+ })) as SerializedUser | null;
314
+ const user = toClientUser(auth.port, raw);
315
+ if (user) auth.currentUser = user;
316
+ return user;
317
+ }
318
+
319
+ function hydrateCred(auth: ClientAuth, raw: SerializedUserCredential): ClientUserCredential {
320
+ const user = makeClientUser(auth.port, raw.user);
321
+ // Eagerly update the mirror so `auth.currentUser` is correct immediately
322
+ // after the await, before the broadcast stream lands.
323
+ auth.currentUser = user;
324
+ return { user, providerId: raw.providerId, operationType: raw.operationType };
325
+ }
326
+
327
+ // ─── Persistence ──────────────────────────────────────────────────────────
328
+
329
+ /** Persistence markers — mirror `firebase/auth` / `pyric/auth`. */
330
+ export const inMemoryPersistence = { type: 'NONE' } as const;
331
+
332
+ export const browserSessionPersistence = { type: 'SESSION' } as const;
333
+
334
+ export const browserLocalPersistence = { type: 'LOCAL' } as const;
335
+
336
+ export type ClientPersistence = { readonly type: AuthPersistenceMode };
337
+
338
+ /**
339
+ * Record the session-persistence mode on the worker (surface parity). The
340
+ * effective persistence is CLIENT-side (#754): the entry adapter mirrors the
341
+ * mode into the page's SessionStore, which decides where — or whether — this
342
+ * tab's session uid is stored for reload restore.
343
+ */
344
+ export async function setPersistence(
345
+ auth: ClientAuth,
346
+ persistence: ClientPersistence,
347
+ ): Promise<void> {
348
+ await rpc(auth.port, {
349
+ t: 'op', id: nextId(), method: 'auth.setPersistence', mode: persistence.type,
350
+ });
351
+ }
352
+
353
+ // ─── Observers (streaming subs) ────────────────────────────────────────────
354
+
355
+ /**
356
+ * Subscribe to auth-state changes. Mirrors `firebase/auth`'s
357
+ * `onAuthStateChanged`. Fires immediately with THIS PORT's current session,
358
+ * then on every change to it (#754: sessions are per-port — another tab's
359
+ * sign-in is a different user, not an update to this one). Updates the
360
+ * handle's `currentUser` mirror before invoking the callback.
361
+ */
362
+ export function onAuthStateChanged(
363
+ auth: ClientAuth,
364
+ callback: (user: ClientUser | null) => void,
365
+ ): Unsubscribe {
366
+ return openAuthSub(auth, 'authState', callback);
367
+ }
368
+
369
+ /**
370
+ * Subscribe to ID-token changes. Mirrors `firebase/auth`'s
371
+ * `onIdTokenChanged` — fires on THIS PORT's identity transitions (per-port
372
+ * sessions, #754).
373
+ */
374
+ export function onIdTokenChanged(
375
+ auth: ClientAuth,
376
+ callback: (user: ClientUser | null) => void,
377
+ ): Unsubscribe {
378
+ return openAuthSub(auth, 'idToken', callback);
379
+ }
380
+
381
+ function openAuthSub(
382
+ auth: ClientAuth,
383
+ target: 'authState' | 'idToken',
384
+ callback: (user: ClientUser | null) => void,
385
+ ): Unsubscribe {
386
+ const subId = nextSubId();
387
+ const port = auth.port;
388
+
389
+ openSnapshotSubscription(port, subId, {
390
+ port,
391
+ next: (raw) => {
392
+ const user = toClientUser(port, raw as SerializedUser | null);
393
+ auth.currentUser = user;
394
+ callback(user);
395
+ },
396
+ }, { t: 'sub', subId, target } satisfies InboundMessage);
397
+
398
+ return () => {
399
+ closeSubscription(port, subId);
400
+ };
401
+ }
402
+
403
+ // ─── Token accessors (top-level mirrors) ──────────────────────────────────
404
+
405
+ /** Top-level mirror of `firebase/auth`'s `getIdToken(user)`. */
406
+ export async function getIdToken(user: ClientUser, forceRefresh?: boolean): Promise<string> {
407
+ return user.getIdToken(forceRefresh);
408
+ }
409
+
410
+ /** Top-level mirror of `firebase/auth`'s `getIdTokenResult(user)`. */
411
+ export async function getIdTokenResult(
412
+ user: ClientUser,
413
+ forceRefresh?: boolean,
414
+ ): Promise<SerializedIdTokenResult> {
415
+ return user.getIdTokenResult(forceRefresh);
416
+ }
417
+
418
+ /**
419
+ * Top-level mirror of `firebase/auth`'s `updateProfile(user, profile)` over
420
+ * the worker. RPCs `auth.updateProfile` for THIS PORT's session (the worker
421
+ * owns the real `User`), then mutates the passed `user` mirror in place with
422
+ * the returned fields so a synchronous read right after the await is
423
+ * consistent. `null` clears a field; an absent field is left untouched.
424
+ *
425
+ * The port is recovered from the hidden {@link CLIENT_USER_PORT} handle stamped
426
+ * on every `ClientUser`, so this works without an `auth` handle in scope.
427
+ */
428
+ export async function updateProfile(
429
+ user: ClientUser,
430
+ profile: { displayName?: string | null; photoURL?: string | null },
431
+ ): Promise<void> {
432
+ const port = (user as { [CLIENT_USER_PORT]?: ClientPort })[CLIENT_USER_PORT];
433
+ if (!port) {
434
+ const err = new Error(
435
+ 'updateProfile: unrecognized user — was it produced by a worker-path sign-in?',
436
+ ) as Error & { code: string };
437
+ err.code = 'auth/invalid-user-token';
438
+ throw err;
439
+ }
440
+ const raw = (await rpc(port, {
441
+ t: 'op', id: nextId(), method: 'auth.updateProfile',
442
+ displayName: profile.displayName, photoURL: profile.photoURL,
443
+ })) as SerializedUser;
444
+ // Mutate the passed mirror in place (readonly at the type level, plain data
445
+ // at runtime) so held references reflect the change immediately.
446
+ const mutable = user as { -readonly [K in keyof ClientUser]: ClientUser[K] };
447
+ mutable.displayName = raw.displayName;
448
+ mutable.photoURL = raw.photoURL;
449
+ mutable.providerData = raw.providerData;
450
+ }
451
+
452
+ // ─── Provider flows — NOT supported over the worker yet ───────────────────
453
+
454
+ /**
455
+ * Provider sign-in (`signInWithCredential`, `signInWithPopup`,
456
+ * `signInWithRedirect`) needs the AuthFlowResolver, which lives in-page and
457
+ * can't cross the worker port. NOT SUPPORTED over the SharedWorker in v1 —
458
+ * a clear error rather than silent breakage. Tracked as a Phase 2 follow-up:
459
+ * thread the resolver through, or run provider flows in-page and hand the
460
+ * resulting credential to the worker.
461
+ */
462
+ export function signInWithCredential(): Promise<never> {
463
+ return Promise.reject(makeUnsupported('signInWithCredential'));
464
+ }
465
+
466
+ /**
467
+ * `beforeAuthStateChanged` gates a LOCAL identity transition — the
468
+ * worker owns the shared user pool + fires the real transition on its
469
+ * own side of the port, so a page-local gate registered here would
470
+ * either never run (silently useless) or run too late to actually
471
+ * block anything. Rather than accept a callback that quietly does
472
+ * nothing, fail loudly at registration time — same defensive pattern
473
+ * as {@link signInWithCredential}. Follow-up: thread registration
474
+ * through the worker RPC protocol so the gate can run host-side before
475
+ * the worker commits a transition.
476
+ */
477
+ export function beforeAuthStateChanged(): never {
478
+ throw makeUnsupported('beforeAuthStateChanged');
479
+ }
480
+
481
+ function makeUnsupported(api: string): Error & { code: string } {
482
+ const err = new Error(
483
+ `${api} is not supported over the SharedWorker yet (provider flows need ` +
484
+ 'the in-page AuthFlowResolver). Follow-up: thread the resolver through or ' +
485
+ 'run the flow in-page and hand the credential to the worker.',
486
+ ) as Error & { code: string };
487
+ err.code = 'auth/operation-not-supported-in-this-environment';
488
+ return err;
489
+ }