@aglyn/tenant-data-admin 1.0.0-beta.217 → 1.0.0-beta.219

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 (122) hide show
  1. package/package.json +9 -9
  2. package/src/lib/server/account-lock-mail.d.ts +74 -0
  3. package/src/lib/server/account-lock-mail.js +139 -0
  4. package/src/lib/server/account-lock-mail.js.map +1 -0
  5. package/src/lib/server/account-mail.d.ts +41 -0
  6. package/src/lib/server/account-mail.js +52 -0
  7. package/src/lib/server/account-mail.js.map +1 -0
  8. package/src/lib/server/api-v1-description.d.ts +112 -0
  9. package/src/lib/server/api-v1-description.js +126 -0
  10. package/src/lib/server/api-v1-description.js.map +1 -0
  11. package/src/lib/server/api-v1-kit.d.ts +156 -0
  12. package/src/lib/server/api-v1-kit.js +191 -0
  13. package/src/lib/server/api-v1-kit.js.map +1 -0
  14. package/src/lib/server/api-v1-resources.d.ts +144 -0
  15. package/src/lib/server/api-v1-resources.js +126 -0
  16. package/src/lib/server/api-v1-resources.js.map +1 -0
  17. package/src/lib/server/consent-group-change.d.ts +2 -1
  18. package/src/lib/server/consent-group-change.js +41 -16
  19. package/src/lib/server/consent-group-change.js.map +1 -1
  20. package/src/lib/server/contact-email-index.d.ts +22 -0
  21. package/src/lib/server/contact-email-index.js +108 -0
  22. package/src/lib/server/contact-email-index.js.map +1 -1
  23. package/src/lib/server/crm-email-activity.d.ts +1 -1
  24. package/src/lib/server/crm-email-activity.js +1 -1
  25. package/src/lib/server/crm-email-activity.js.map +1 -1
  26. package/src/lib/server/crm-inbound-email.d.ts +23 -0
  27. package/src/lib/server/crm-inbound-email.js +1 -16
  28. package/src/lib/server/crm-inbound-email.js.map +1 -1
  29. package/src/lib/server/crm-records.d.ts +9 -1
  30. package/src/lib/server/crm-records.js +19 -2
  31. package/src/lib/server/crm-records.js.map +1 -1
  32. package/src/lib/server/cron-auth.d.ts +44 -0
  33. package/src/lib/server/cron-auth.js +76 -0
  34. package/src/lib/server/cron-auth.js.map +1 -0
  35. package/src/lib/server/dataset-live-pages.js +6 -6
  36. package/src/lib/server/dataset-live-pages.js.map +1 -1
  37. package/src/lib/server/duplicate-resource.d.ts +26 -0
  38. package/src/lib/server/duplicate-resource.js +47 -25
  39. package/src/lib/server/duplicate-resource.js.map +1 -1
  40. package/src/lib/server/dynamic-list-materialize.js +4 -1
  41. package/src/lib/server/dynamic-list-materialize.js.map +1 -1
  42. package/src/lib/server/email-deliverability.js +21 -6
  43. package/src/lib/server/email-deliverability.js.map +1 -1
  44. package/src/lib/server/email-suppression.d.ts +83 -3
  45. package/src/lib/server/email-suppression.js +229 -3
  46. package/src/lib/server/email-suppression.js.map +1 -1
  47. package/src/lib/server/email-unsubscribe-link.d.ts +8 -5
  48. package/src/lib/server/email-unsubscribe-link.js +7 -7
  49. package/src/lib/server/email-unsubscribe-link.js.map +1 -1
  50. package/src/lib/server/erase.d.ts +19 -33
  51. package/src/lib/server/erase.js +37 -162
  52. package/src/lib/server/erase.js.map +1 -1
  53. package/src/lib/server/host-email-tokens.js +1 -1
  54. package/src/lib/server/host-email-tokens.js.map +1 -1
  55. package/src/lib/server/host-memberships.js +3 -3
  56. package/src/lib/server/host-memberships.js.map +1 -1
  57. package/src/lib/server/lockdown.js +2 -0
  58. package/src/lib/server/lockdown.js.map +1 -1
  59. package/src/lib/server/media-download-tokens.js +2 -1
  60. package/src/lib/server/media-download-tokens.js.map +1 -1
  61. package/src/lib/server/member-email-aliases.d.ts +1 -1
  62. package/src/lib/server/member-email-aliases.js +1 -1
  63. package/src/lib/server/member-email-aliases.js.map +1 -1
  64. package/src/lib/server/new-account-announcement.d.ts +62 -0
  65. package/src/lib/server/new-account-announcement.js +77 -0
  66. package/src/lib/server/new-account-announcement.js.map +1 -0
  67. package/src/lib/server/notifications.d.ts +8 -2
  68. package/src/lib/server/notifications.js +18 -4
  69. package/src/lib/server/notifications.js.map +1 -1
  70. package/src/lib/server/org-member-notice.js +5 -1
  71. package/src/lib/server/org-member-notice.js.map +1 -1
  72. package/src/lib/server/organizations.js +4 -0
  73. package/src/lib/server/organizations.js.map +1 -1
  74. package/src/lib/server/{connect-payout-failure.d.ts → payment-provider-stripe-connect-payouts.d.ts} +3 -0
  75. package/src/lib/server/{connect-payout-failure.js → payment-provider-stripe-connect-payouts.js} +1 -1
  76. package/src/lib/server/payment-provider-stripe-connect-payouts.js.map +1 -0
  77. package/src/lib/server/{connect-account-status.d.ts → payment-provider-stripe-connect-status.d.ts} +3 -0
  78. package/src/lib/server/{connect-account-status.js → payment-provider-stripe-connect-status.js} +1 -1
  79. package/src/lib/server/payment-provider-stripe-connect-status.js.map +1 -0
  80. package/src/lib/server/{stripe-account-mode.d.ts → payment-provider-stripe-connect.d.ts} +45 -70
  81. package/src/lib/server/{stripe-account-mode.js → payment-provider-stripe-connect.js} +78 -52
  82. package/src/lib/server/payment-provider-stripe-connect.js.map +1 -0
  83. package/src/lib/server/payment-provider.d.ts +173 -0
  84. package/src/lib/server/payment-provider.js +70 -0
  85. package/src/lib/server/payment-provider.js.map +1 -0
  86. package/src/lib/server/personal-data-export.js +1 -1
  87. package/src/lib/server/personal-data-export.js.map +1 -1
  88. package/src/lib/server/rate-limit-store.js +13 -3
  89. package/src/lib/server/rate-limit-store.js.map +1 -1
  90. package/src/lib/server/realm-plugins.js +8 -3
  91. package/src/lib/server/realm-plugins.js.map +1 -1
  92. package/src/lib/server/retained-refusals.d.ts +142 -0
  93. package/src/lib/server/retained-refusals.js +325 -0
  94. package/src/lib/server/retained-refusals.js.map +1 -0
  95. package/src/lib/server/risk-notice.js +21 -3
  96. package/src/lib/server/risk-notice.js.map +1 -1
  97. package/src/lib/server/serve-media-cdn.js +2 -2
  98. package/src/lib/server/serve-media-cdn.js.map +1 -1
  99. package/src/lib/server/serve-plugin-fetch.d.ts +17 -7
  100. package/src/lib/server/serve-plugin-fetch.js +19 -8
  101. package/src/lib/server/serve-plugin-fetch.js.map +1 -1
  102. package/src/lib/server/test-firestore-queries.d.ts +7 -0
  103. package/src/lib/server/test-firestore-queries.js +70 -27
  104. package/src/lib/server/test-firestore-queries.js.map +1 -1
  105. package/src/lib/server/upsert-contact.d.ts +46 -1
  106. package/src/lib/server/upsert-contact.js +85 -18
  107. package/src/lib/server/upsert-contact.js.map +1 -1
  108. package/src/tenancy.d.ts +4 -5
  109. package/src/tenancy.js +11 -6
  110. package/src/tenancy.js.map +1 -1
  111. package/src/lib/server/connect-account-status.js.map +0 -1
  112. package/src/lib/server/connect-payout-failure.js.map +0 -1
  113. package/src/lib/server/contact-lifecycle-floor.d.ts +0 -89
  114. package/src/lib/server/contact-lifecycle-floor.js +0 -91
  115. package/src/lib/server/contact-lifecycle-floor.js.map +0 -1
  116. package/src/lib/server/contact-merge.d.ts +0 -55
  117. package/src/lib/server/contact-merge.js +0 -288
  118. package/src/lib/server/contact-merge.js.map +0 -1
  119. package/src/lib/server/form-dataset-binding-token.d.ts +0 -35
  120. package/src/lib/server/form-dataset-binding-token.js +0 -107
  121. package/src/lib/server/form-dataset-binding-token.js.map +0 -1
  122. package/src/lib/server/stripe-account-mode.js.map +0 -1
@@ -17,7 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  */ import { isPluginNetworkAllowed, PLUGIN_FETCH_MAX_BODY_BYTES } from "@aglyn/aglyn/server";
18
18
  import { lookup } from "node:dns/promises";
19
19
  import { isIPv4, isIPv6 } from "net";
20
- import { Agent } from "undici";
20
+ import { Agent, Dispatcher1Wrapper } from "undici";
21
21
  import { firebaseAdmin } from "./firebase-admin.js";
22
22
  const FETCH_TIMEOUT_MS = 8000;
23
23
  /**
@@ -99,14 +99,25 @@ const FETCH_TIMEOUT_MS = 8000;
99
99
  * is a **declared direct dependency** for exactly this reason: it used to
100
100
  * arrive only as a transitive of the dev-only `jsdom`, which meant a
101
101
  * `--omit=dev` install had no `undici` at all and a jsdom bump silently
102
- * re-versioned the HTTP client behind a production security control. undici 8
103
- * rejects this `lookup` shape with `UND_ERR_INVALID_ARG` on every request —
104
- * fail-closed, but the plugin-fetch route stops working outright. The spec
105
- * drives a real request through this dispatcher so that break is a red test,
106
- * not a production discovery.
102
+ * re-versioned the HTTP client behind a production security control. The spec
103
+ * drives a real request through this dispatcher so that a transport break is a
104
+ * red test, not a production discovery.
105
+ *
106
+ * ## Why the agent is wrapped
107
+ *
108
+ * The request goes through Node's BUILT-IN `fetch`, which is undici 7 inside
109
+ * Node 24 and speaks the v1 handler protocol. An undici 8 `Agent` speaks v2, so
110
+ * handing it to the built-in `fetch` bare fails every request with
111
+ * `InvalidArgumentError: invalid onRequestStart method` — fail-closed, but the
112
+ * plugin-fetch route stops working outright. `Dispatcher1Wrapper` is undici's
113
+ * own bridge for exactly that pairing, and it forwards `close()` to the agent.
114
+ *
115
+ * `allowH2: false` keeps the transport HTTP/1.1, which is what this pin was
116
+ * written and measured against; undici 8 turned HTTP/2 on by default.
107
117
  */ export function createPinnedDispatcher(pinnedIp) {
108
118
  const family = isIPv6(pinnedIp) ? 6 : 4;
109
- return new Agent({
119
+ return new Dispatcher1Wrapper(new Agent({
120
+ allowH2: false,
110
121
  connect: {
111
122
  lookup: (_host, options, cb)=>options && options.all ? cb(null, [
112
123
  {
@@ -115,7 +126,7 @@ const FETCH_TIMEOUT_MS = 8000;
115
126
  }
116
127
  ]) : cb(null, pinnedIp, family)
117
128
  }
118
- });
129
+ }));
119
130
  }
120
131
  /**
121
132
  * Host-mediated plugin fetch (AGL-191). The sandboxed plugin can't reach
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/serve-plugin-fetch.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n isPluginNetworkAllowed,\n PLUGIN_FETCH_MAX_BODY_BYTES,\n} from '@aglyn/aglyn/server'\nimport { lookup } from 'dns/promises'\nimport { isIPv4, isIPv6 } from 'net'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { Agent } from 'undici'\nimport { firebaseAdmin } from './firebase-admin'\n\nconst FETCH_TIMEOUT_MS = 8000\n/**\n * Redirect hops the proxy will follow, each one re-validated (AGL-1881).\n * Small on purpose: a legitimate API redirects once (http->https is refused\n * outright by the allowlist, so in practice this is a path or host alias).\n */\nconst MAX_REDIRECT_HOPS = 3\n/** The statuses `redirect: 'manual'` hands back instead of following. */\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])\n\n/** True for loopback / private / link-local / CGNAT / ULA addresses. */\nfunction isPrivateIp(ip: string): boolean {\n if (isIPv4(ip)) {\n const [a, b] = ip.split('.').map(Number)\n if (a === 0 || a === 10 || a === 127) return true\n if (a === 169 && b === 254) return true // link-local + cloud metadata\n if (a === 172 && b >= 16 && b <= 31) return true\n if (a === 192 && b === 168) return true\n if (a === 100 && b >= 64 && b <= 127) return true // CGNAT\n // The ranges AGL-1881 found missing. None is routable on the public\n // internet, so refusing them costs a plugin nothing:\n if (a === 192 && b === 0) return true // 192.0.0.0/24 IETF protocol\n if (a === 198 && (b === 18 || b === 19)) return true // 198.18/15 benchmark\n if (a >= 224) return true // 224/4 multicast, 240/4 reserved, broadcast\n return false\n }\n if (isIPv6(ip)) {\n const low = ip.toLowerCase()\n if (low === '::1' || low === '::') return true\n if (low.startsWith('fc') || low.startsWith('fd')) return true // ULA\n // Link-local is fe80::/10 — fe80 through febf, not the fe80 prefix\n // alone (AGL-1881). `fec0::/10` site-local is deprecated but inside the\n // same span, so one test covers both.\n if (/^fe[89ab]/.test(low)) return true // link-local fe80::/10\n if (low.startsWith('::ffff:')) return isPrivateIp(low.slice(7)) // v4-mapped\n return false\n }\n return true // unparseable → refuse\n}\n\n/**\n * SSRF hardening (AGL-515): even an https, allowlisted origin can resolve to\n * an internal address (a plugin author pointing DNS at 127.0.0.1 / the cloud\n * metadata endpoint). Resolve the host and return a single validated PUBLIC\n * address to pin the connection to (null if any resolved address is private).\n * Pinning the exact IP the fetch connects to closes the DNS-rebinding TOCTOU —\n * the name can't re-resolve to an internal target between check and connect.\n */\nexport async function resolvePublicIp(hostname: string): Promise<string | null> {\n try {\n const addresses = await lookup(hostname, { all: true })\n if (!addresses.length) return null\n if (addresses.some((a) => isPrivateIp(a.address))) return null\n return addresses[0].address\n } catch {\n return null\n }\n}\n\n/**\n * Build the dispatcher that pins the socket to `pinnedIp` (AGL-515). A custom\n * `connect.lookup` forces the connection to the address `resolvePublicIp`\n * already validated, so the name can't be rebound to an internal target\n * between the check and the connect. TLS SNI / certificate validation still\n * use the original hostname, because only the address resolution is replaced.\n *\n * Exported so the transport itself is covered by a spec (AGL-2480). `undici`\n * is a **declared direct dependency** for exactly this reason: it used to\n * arrive only as a transitive of the dev-only `jsdom`, which meant a\n * `--omit=dev` install had no `undici` at all and a jsdom bump silently\n * re-versioned the HTTP client behind a production security control. undici 8\n * rejects this `lookup` shape with `UND_ERR_INVALID_ARG` on every request —\n * fail-closed, but the plugin-fetch route stops working outright. The spec\n * drives a real request through this dispatcher so that break is a red test,\n * not a production discovery.\n */\nexport function createPinnedDispatcher(pinnedIp: string): Agent {\n const family = isIPv6(pinnedIp) ? 6 : 4\n return new Agent({\n connect: {\n lookup: (_host, options, cb) =>\n options && options.all\n ? cb(null, [{ address: pinnedIp, family }])\n : cb(null, pinnedIp, family),\n },\n })\n}\n\n/**\n * Host-mediated plugin fetch (AGL-191). The sandboxed plugin can't reach\n * the network directly under a strict plugin-origin CSP, so `PluginFrame`\n * forwards a `fetch-request` here and the host proxies it. Mounted in BOTH\n * apps (console for the editor/preview, tenant for the published site) so\n * plugin fetches work wherever the plugin renders — hence public, no\n * admin auth: the security boundary is the ALLOWLIST, re-derived from the\n * host's server-side install record and never trusting the client's claim.\n * https-only, exact-origin, method/body/time capped, and no host\n * credentials are ever forwarded — a plugin can only reach origins it\n * declared at install (surfaced on the install screen).\n *\n * Abuse note: this uses platform egress to origins the plugin already\n * declared; a per-listing rate limit is a sensible follow-up.\n */\nexport async function servePluginFetch(\n req: NextApiRequest,\n res: NextApiResponse,\n): Promise<void> {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ ok: false, status: 0, error: 'Method not allowed' })\n return\n }\n const body =\n typeof req.body === 'string' ? safeJson(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const listingId = String(body.listingId ?? '')\n const url = String(body.url ?? '')\n const method = body.method === 'POST' ? 'POST' : 'GET'\n const requestBody =\n typeof body.body === 'string' ? (body.body as string) : undefined\n if (!hostId || !listingId || !url) {\n res.status(400).json({ ok: false, status: 0, error: 'Bad request' })\n return\n }\n if (requestBody && Buffer.byteLength(requestBody) > PLUGIN_FETCH_MAX_BODY_BYTES) {\n res.status(413).json({ ok: false, status: 0, error: 'Body too large' })\n return\n }\n\n try {\n const install = (\n await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('installs')\n .doc(listingId)\n .get()\n ).data() as { manifest?: { capabilities?: unknown } } | undefined\n if (!install?.manifest) {\n res.status(404).json({ ok: false, status: 0, error: 'Not installed' })\n return\n }\n if (\n !isPluginNetworkAllowed(\n url,\n (install.manifest as { capabilities?: any }).capabilities,\n )\n ) {\n res\n .status(403)\n .json({ ok: false, status: 0, error: 'URL not in allowlist' })\n return\n }\n // Follow redirects OURSELVES, re-validating every hop (AGL-1881).\n //\n // `fetch` defaults to `redirect: 'follow'`, and every control above runs\n // on the FIRST url only: the allowlist, the https-only check and the\n // pinned dispatcher. A declared, allowlisted, genuinely public origin\n // answering `302 Location: http://169.254.169.254/...` therefore reached\n // the cloud metadata service and returned its body to the caller — on an\n // unauthenticated route. The IP pin did not stop it: Node skips a custom\n // `connect.lookup` when the target is an IP literal, so the redirect\n // connected directly and the pinned lookup was consulted exactly once,\n // for the original hostname.\n //\n // `redirect: 'manual'` hands us each hop instead. Every one re-enters the\n // same two gates the first url passed — the manifest allowlist (which is\n // also what enforces https and the exact origin) and a fresh\n // `resolvePublicIp` with its own pinned dispatcher — so a redirect can\n // only ever reach somewhere the plugin could have named directly.\n let currentUrl = url\n let upstream: Response\n for (let hop = 0; ; hop++) {\n if (hop > MAX_REDIRECT_HOPS) {\n res\n .status(403)\n .json({ ok: false, status: 0, error: 'Too many redirects' })\n return\n }\n const pinnedIp = await resolvePublicIp(new URL(currentUrl).hostname)\n if (!pinnedIp) {\n res.status(403).json({\n ok: false,\n status: 0,\n error: 'URL resolves to a non-public address',\n })\n return\n }\n\n // Pin the socket to the address we just validated (AGL-515).\n const dispatcher = createPinnedDispatcher(pinnedIp)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)\n // `dispatcher` is an undici extension not in the DOM RequestInit type;\n // assigning to a variable first avoids the object-literal excess-property\n // check while still passing it through to the (undici) global fetch.\n const requestInit = {\n method,\n signal: controller.signal,\n dispatcher,\n redirect: 'manual' as const,\n headers: { Accept: 'application/json, text/*;q=0.9, */*;q=0.1' },\n ...(method === 'POST' && requestBody ? { body: requestBody } : {}),\n }\n try {\n upstream = await fetch(currentUrl, requestInit)\n } finally {\n clearTimeout(timeout)\n await dispatcher.close().catch(() => undefined)\n }\n\n if (!REDIRECT_STATUSES.has(upstream.status)) break\n\n const location = upstream.headers.get('location')\n if (!location) break // a redirect status with no target is just a body\n let nextUrl: string\n try {\n nextUrl = new URL(location, currentUrl).toString()\n } catch {\n res\n .status(403)\n .json({ ok: false, status: 0, error: 'Redirect target is not a URL' })\n return\n }\n if (\n !isPluginNetworkAllowed(\n nextUrl,\n (install.manifest as { capabilities?: any }).capabilities,\n )\n ) {\n res.status(403).json({\n ok: false,\n status: 0,\n error: 'Redirect target not in allowlist',\n })\n return\n }\n currentUrl = nextUrl\n }\n\n const text = await upstream.text()\n const clipped =\n Buffer.byteLength(text) > PLUGIN_FETCH_MAX_BODY_BYTES\n ? text.slice(0, PLUGIN_FETCH_MAX_BODY_BYTES)\n : text\n res.status(200).json({\n ok: upstream.ok,\n status: upstream.status,\n body: clipped,\n })\n } catch (error) {\n console.error('servePluginFetch failed', hostId, listingId, error)\n res.status(502).json({ ok: false, status: 0, error: 'Fetch failed' })\n }\n}\n\nfunction safeJson(value: string): Record<string, unknown> {\n try {\n return JSON.parse(value)\n } catch {\n return {}\n }\n}\n"],"names":["isPluginNetworkAllowed","PLUGIN_FETCH_MAX_BODY_BYTES","lookup","isIPv4","isIPv6","Agent","firebaseAdmin","FETCH_TIMEOUT_MS","MAX_REDIRECT_HOPS","REDIRECT_STATUSES","Set","isPrivateIp","ip","a","b","split","map","Number","low","toLowerCase","startsWith","test","slice","resolvePublicIp","hostname","addresses","all","length","some","address","createPinnedDispatcher","pinnedIp","family","connect","_host","options","cb","servePluginFetch","req","res","body","method","setHeader","status","json","ok","error","safeJson","hostId","String","listingId","url","requestBody","undefined","Buffer","byteLength","install","app","firestore","collection","doc","get","data","manifest","capabilities","currentUrl","upstream","hop","URL","dispatcher","controller","AbortController","timeout","setTimeout","abort","requestInit","signal","redirect","headers","Accept","fetch","clearTimeout","close","catch","has","location","nextUrl","toString","text","clipped","console","value","JSON","parse"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/serve-plugin-fetch.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n isPluginNetworkAllowed,\n PLUGIN_FETCH_MAX_BODY_BYTES,\n} from '@aglyn/aglyn/server'\nimport { lookup } from 'dns/promises'\nimport { isIPv4, isIPv6 } from 'net'\nimport type { NextApiRequest, NextApiResponse } from 'next'\nimport { Agent, type Dispatcher, Dispatcher1Wrapper } from 'undici'\nimport { firebaseAdmin } from './firebase-admin'\n\nconst FETCH_TIMEOUT_MS = 8000\n/**\n * Redirect hops the proxy will follow, each one re-validated (AGL-1881).\n * Small on purpose: a legitimate API redirects once (http->https is refused\n * outright by the allowlist, so in practice this is a path or host alias).\n */\nconst MAX_REDIRECT_HOPS = 3\n/** The statuses `redirect: 'manual'` hands back instead of following. */\nconst REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])\n\n/** True for loopback / private / link-local / CGNAT / ULA addresses. */\nfunction isPrivateIp(ip: string): boolean {\n if (isIPv4(ip)) {\n const [a, b] = ip.split('.').map(Number)\n if (a === 0 || a === 10 || a === 127) return true\n if (a === 169 && b === 254) return true // link-local + cloud metadata\n if (a === 172 && b >= 16 && b <= 31) return true\n if (a === 192 && b === 168) return true\n if (a === 100 && b >= 64 && b <= 127) return true // CGNAT\n // The ranges AGL-1881 found missing. None is routable on the public\n // internet, so refusing them costs a plugin nothing:\n if (a === 192 && b === 0) return true // 192.0.0.0/24 IETF protocol\n if (a === 198 && (b === 18 || b === 19)) return true // 198.18/15 benchmark\n if (a >= 224) return true // 224/4 multicast, 240/4 reserved, broadcast\n return false\n }\n if (isIPv6(ip)) {\n const low = ip.toLowerCase()\n if (low === '::1' || low === '::') return true\n if (low.startsWith('fc') || low.startsWith('fd')) return true // ULA\n // Link-local is fe80::/10 — fe80 through febf, not the fe80 prefix\n // alone (AGL-1881). `fec0::/10` site-local is deprecated but inside the\n // same span, so one test covers both.\n if (/^fe[89ab]/.test(low)) return true // link-local fe80::/10\n if (low.startsWith('::ffff:')) return isPrivateIp(low.slice(7)) // v4-mapped\n return false\n }\n return true // unparseable → refuse\n}\n\n/**\n * SSRF hardening (AGL-515): even an https, allowlisted origin can resolve to\n * an internal address (a plugin author pointing DNS at 127.0.0.1 / the cloud\n * metadata endpoint). Resolve the host and return a single validated PUBLIC\n * address to pin the connection to (null if any resolved address is private).\n * Pinning the exact IP the fetch connects to closes the DNS-rebinding TOCTOU —\n * the name can't re-resolve to an internal target between check and connect.\n */\nexport async function resolvePublicIp(hostname: string): Promise<string | null> {\n try {\n const addresses = await lookup(hostname, { all: true })\n if (!addresses.length) return null\n if (addresses.some((a) => isPrivateIp(a.address))) return null\n return addresses[0].address\n } catch {\n return null\n }\n}\n\n/**\n * Build the dispatcher that pins the socket to `pinnedIp` (AGL-515). A custom\n * `connect.lookup` forces the connection to the address `resolvePublicIp`\n * already validated, so the name can't be rebound to an internal target\n * between the check and the connect. TLS SNI / certificate validation still\n * use the original hostname, because only the address resolution is replaced.\n *\n * Exported so the transport itself is covered by a spec (AGL-2480). `undici`\n * is a **declared direct dependency** for exactly this reason: it used to\n * arrive only as a transitive of the dev-only `jsdom`, which meant a\n * `--omit=dev` install had no `undici` at all and a jsdom bump silently\n * re-versioned the HTTP client behind a production security control. The spec\n * drives a real request through this dispatcher so that a transport break is a\n * red test, not a production discovery.\n *\n * ## Why the agent is wrapped\n *\n * The request goes through Node's BUILT-IN `fetch`, which is undici 7 inside\n * Node 24 and speaks the v1 handler protocol. An undici 8 `Agent` speaks v2, so\n * handing it to the built-in `fetch` bare fails every request with\n * `InvalidArgumentError: invalid onRequestStart method` — fail-closed, but the\n * plugin-fetch route stops working outright. `Dispatcher1Wrapper` is undici's\n * own bridge for exactly that pairing, and it forwards `close()` to the agent.\n *\n * `allowH2: false` keeps the transport HTTP/1.1, which is what this pin was\n * written and measured against; undici 8 turned HTTP/2 on by default.\n */\nexport function createPinnedDispatcher(pinnedIp: string): Dispatcher {\n const family = isIPv6(pinnedIp) ? 6 : 4\n return new Dispatcher1Wrapper(\n new Agent({\n allowH2: false,\n connect: {\n lookup: (_host, options, cb) =>\n options && options.all\n ? cb(null, [{ address: pinnedIp, family }])\n : cb(null, pinnedIp, family),\n },\n }),\n )\n}\n\n/**\n * Host-mediated plugin fetch (AGL-191). The sandboxed plugin can't reach\n * the network directly under a strict plugin-origin CSP, so `PluginFrame`\n * forwards a `fetch-request` here and the host proxies it. Mounted in BOTH\n * apps (console for the editor/preview, tenant for the published site) so\n * plugin fetches work wherever the plugin renders — hence public, no\n * admin auth: the security boundary is the ALLOWLIST, re-derived from the\n * host's server-side install record and never trusting the client's claim.\n * https-only, exact-origin, method/body/time capped, and no host\n * credentials are ever forwarded — a plugin can only reach origins it\n * declared at install (surfaced on the install screen).\n *\n * Abuse note: this uses platform egress to origins the plugin already\n * declared; a per-listing rate limit is a sensible follow-up.\n */\nexport async function servePluginFetch(\n req: NextApiRequest,\n res: NextApiResponse,\n): Promise<void> {\n if (req.method !== 'POST') {\n res.setHeader('Allow', 'POST')\n res.status(405).json({ ok: false, status: 0, error: 'Method not allowed' })\n return\n }\n const body =\n typeof req.body === 'string' ? safeJson(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const listingId = String(body.listingId ?? '')\n const url = String(body.url ?? '')\n const method = body.method === 'POST' ? 'POST' : 'GET'\n const requestBody =\n typeof body.body === 'string' ? (body.body as string) : undefined\n if (!hostId || !listingId || !url) {\n res.status(400).json({ ok: false, status: 0, error: 'Bad request' })\n return\n }\n if (requestBody && Buffer.byteLength(requestBody) > PLUGIN_FETCH_MAX_BODY_BYTES) {\n res.status(413).json({ ok: false, status: 0, error: 'Body too large' })\n return\n }\n\n try {\n const install = (\n await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('installs')\n .doc(listingId)\n .get()\n ).data() as { manifest?: { capabilities?: unknown } } | undefined\n if (!install?.manifest) {\n res.status(404).json({ ok: false, status: 0, error: 'Not installed' })\n return\n }\n if (\n !isPluginNetworkAllowed(\n url,\n (install.manifest as { capabilities?: any }).capabilities,\n )\n ) {\n res\n .status(403)\n .json({ ok: false, status: 0, error: 'URL not in allowlist' })\n return\n }\n // Follow redirects OURSELVES, re-validating every hop (AGL-1881).\n //\n // `fetch` defaults to `redirect: 'follow'`, and every control above runs\n // on the FIRST url only: the allowlist, the https-only check and the\n // pinned dispatcher. A declared, allowlisted, genuinely public origin\n // answering `302 Location: http://169.254.169.254/...` therefore reached\n // the cloud metadata service and returned its body to the caller — on an\n // unauthenticated route. The IP pin did not stop it: Node skips a custom\n // `connect.lookup` when the target is an IP literal, so the redirect\n // connected directly and the pinned lookup was consulted exactly once,\n // for the original hostname.\n //\n // `redirect: 'manual'` hands us each hop instead. Every one re-enters the\n // same two gates the first url passed — the manifest allowlist (which is\n // also what enforces https and the exact origin) and a fresh\n // `resolvePublicIp` with its own pinned dispatcher — so a redirect can\n // only ever reach somewhere the plugin could have named directly.\n let currentUrl = url\n let upstream: Response\n for (let hop = 0; ; hop++) {\n if (hop > MAX_REDIRECT_HOPS) {\n res\n .status(403)\n .json({ ok: false, status: 0, error: 'Too many redirects' })\n return\n }\n const pinnedIp = await resolvePublicIp(new URL(currentUrl).hostname)\n if (!pinnedIp) {\n res.status(403).json({\n ok: false,\n status: 0,\n error: 'URL resolves to a non-public address',\n })\n return\n }\n\n // Pin the socket to the address we just validated (AGL-515).\n const dispatcher = createPinnedDispatcher(pinnedIp)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)\n // `dispatcher` is an undici extension not in the DOM RequestInit type;\n // assigning to a variable first avoids the object-literal excess-property\n // check while still passing it through to the (undici) global fetch.\n const requestInit = {\n method,\n signal: controller.signal,\n dispatcher,\n redirect: 'manual' as const,\n headers: { Accept: 'application/json, text/*;q=0.9, */*;q=0.1' },\n ...(method === 'POST' && requestBody ? { body: requestBody } : {}),\n }\n try {\n upstream = await fetch(currentUrl, requestInit)\n } finally {\n clearTimeout(timeout)\n await dispatcher.close().catch(() => undefined)\n }\n\n if (!REDIRECT_STATUSES.has(upstream.status)) break\n\n const location = upstream.headers.get('location')\n if (!location) break // a redirect status with no target is just a body\n let nextUrl: string\n try {\n nextUrl = new URL(location, currentUrl).toString()\n } catch {\n res\n .status(403)\n .json({ ok: false, status: 0, error: 'Redirect target is not a URL' })\n return\n }\n if (\n !isPluginNetworkAllowed(\n nextUrl,\n (install.manifest as { capabilities?: any }).capabilities,\n )\n ) {\n res.status(403).json({\n ok: false,\n status: 0,\n error: 'Redirect target not in allowlist',\n })\n return\n }\n currentUrl = nextUrl\n }\n\n const text = await upstream.text()\n const clipped =\n Buffer.byteLength(text) > PLUGIN_FETCH_MAX_BODY_BYTES\n ? text.slice(0, PLUGIN_FETCH_MAX_BODY_BYTES)\n : text\n res.status(200).json({\n ok: upstream.ok,\n status: upstream.status,\n body: clipped,\n })\n } catch (error) {\n console.error('servePluginFetch failed', hostId, listingId, error)\n res.status(502).json({ ok: false, status: 0, error: 'Fetch failed' })\n }\n}\n\nfunction safeJson(value: string): Record<string, unknown> {\n try {\n return JSON.parse(value)\n } catch {\n return {}\n }\n}\n"],"names":["isPluginNetworkAllowed","PLUGIN_FETCH_MAX_BODY_BYTES","lookup","isIPv4","isIPv6","Agent","Dispatcher1Wrapper","firebaseAdmin","FETCH_TIMEOUT_MS","MAX_REDIRECT_HOPS","REDIRECT_STATUSES","Set","isPrivateIp","ip","a","b","split","map","Number","low","toLowerCase","startsWith","test","slice","resolvePublicIp","hostname","addresses","all","length","some","address","createPinnedDispatcher","pinnedIp","family","allowH2","connect","_host","options","cb","servePluginFetch","req","res","body","method","setHeader","status","json","ok","error","safeJson","hostId","String","listingId","url","requestBody","undefined","Buffer","byteLength","install","app","firestore","collection","doc","get","data","manifest","capabilities","currentUrl","upstream","hop","URL","dispatcher","controller","AbortController","timeout","setTimeout","abort","requestInit","signal","redirect","headers","Accept","fetch","clearTimeout","close","catch","has","location","nextUrl","toString","text","clipped","console","value","JSON","parse"],"mappings":"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"}
@@ -31,6 +31,13 @@ export interface QueryFakeFirestore {
31
31
  /** Committed writes since the last reset. */
32
32
  writes(): number;
33
33
  resetWrites(): void;
34
+ /**
35
+ * Read ROUND TRIPS since the last reset: one per document `get`, query
36
+ * `get`, `count()`, `getAll` or transaction read, however many documents
37
+ * it returned — the unit a request's latency is spent in.
38
+ */
39
+ reads(): number;
40
+ resetReads(): void;
34
41
  /** The clock `serverTimestamp()` and every `updateTime` read. */
35
42
  setNow(ms: number): void;
36
43
  /** Makes the next writes to `path` fail with `error` — a stand-in for an outage. */
@@ -34,7 +34,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
34
34
  * - `update()` needs the document to exist, replaces the value at each
35
35
  * (dotted) path whole, and fails its `lastUpdateTime` precondition when
36
36
  * the document was written since; `create()` fails on an existing one.
37
- * The error codes are gRPC's: 5, 6, 9.
37
+ * The error codes are gRPC's: 5, 6, 9. A `set()` with `mergeFields`
38
+ * replaces the value at each named path whole, and creates the document
39
+ * when it is missing.
38
40
  * - `FieldValue` sentinels are applied, not stored: `delete`,
39
41
  * `serverTimestamp` (the fake clock), `increment`, `arrayUnion`,
40
42
  * `arrayRemove`.
@@ -133,6 +135,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
133
135
  let nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.UTC(2026, 8, 24);
134
136
  /** Keeps every write's `updateTime` distinct even on a frozen clock. */ let tick = 0;
135
137
  let writeCount = 0;
138
+ let readCount = 0;
136
139
  const failing = new Map();
137
140
  let autoIds = 0;
138
141
  const stamp = ()=>Timestamp.fromMillis(nowMs + (tick += 0.001));
@@ -266,10 +269,12 @@ export function queryFakeFirestore(seed = {}, options = {}) {
266
269
  });
267
270
  return at;
268
271
  }
269
- if (write.type === 'set' && write.merge) {
272
+ if (write.type === 'set' && write.fields) {
270
273
  var _ref3, _ref4;
271
274
  const data = clone((_ref3 = existing == null ? void 0 : existing.data) != null ? _ref3 : {});
272
- mergeInto(data, write.data);
275
+ for (const path of write.fields){
276
+ put(data, path.split('.'), clone(valueAt(write.data, path)), valueAt(existing == null ? void 0 : existing.data, path));
277
+ }
273
278
  store.set(write.path, {
274
279
  data,
275
280
  createTime: (_ref4 = existing == null ? void 0 : existing.createTime) != null ? _ref4 : at,
@@ -277,6 +282,17 @@ export function queryFakeFirestore(seed = {}, options = {}) {
277
282
  });
278
283
  return at;
279
284
  }
285
+ if (write.type === 'set' && write.merge) {
286
+ var _ref5, _ref6;
287
+ const data = clone((_ref5 = existing == null ? void 0 : existing.data) != null ? _ref5 : {});
288
+ mergeInto(data, write.data);
289
+ store.set(write.path, {
290
+ data,
291
+ createTime: (_ref6 = existing == null ? void 0 : existing.createTime) != null ? _ref6 : at,
292
+ updateTime: at
293
+ });
294
+ return at;
295
+ }
280
296
  const data = {};
281
297
  mergeInto(data, write.data);
282
298
  store.set(write.path, {
@@ -286,6 +302,16 @@ export function queryFakeFirestore(seed = {}, options = {}) {
286
302
  });
287
303
  return at;
288
304
  }
305
+ function setWrite(path, data, setOptions) {
306
+ return _extends({
307
+ type: 'set',
308
+ path,
309
+ data,
310
+ merge: (setOptions == null ? void 0 : setOptions.merge) === true
311
+ }, (setOptions == null ? void 0 : setOptions.mergeFields) ? {
312
+ fields: setOptions.mergeFields.map(fieldName)
313
+ } : {});
314
+ }
289
315
  function snapshot(path) {
290
316
  const stored = store.get(path);
291
317
  const id = path.split('/').pop();
@@ -306,13 +332,11 @@ export function queryFakeFirestore(seed = {}, options = {}) {
306
332
  id: segments[segments.length - 1],
307
333
  path,
308
334
  collection: (name)=>collectionRef(`${path}/${name}`),
309
- get: async ()=>snapshot(path),
310
- set: async (data, setOptions)=>run({
311
- type: 'set',
312
- path,
313
- data,
314
- merge: (setOptions == null ? void 0 : setOptions.merge) === true
315
- }),
335
+ get: async ()=>{
336
+ readCount += 1;
337
+ return snapshot(path);
338
+ },
339
+ set: async (data, setOptions)=>run(setWrite(path, data, setOptions)),
316
340
  update: async (data, precondition)=>run({
317
341
  type: 'update',
318
342
  path,
@@ -334,6 +358,9 @@ export function queryFakeFirestore(seed = {}, options = {}) {
334
358
  Object.defineProperty(ref, 'parent', {
335
359
  get: ()=>collectionRef(segments.slice(0, -1).join('/'))
336
360
  });
361
+ Object.defineProperty(ref, 'firestore', {
362
+ get: ()=>firestore
363
+ });
337
364
  return ref;
338
365
  }
339
366
  function run(write) {
@@ -414,6 +441,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
414
441
  })),
415
442
  count: ()=>({
416
443
  get: async ()=>{
444
+ readCount += 1;
417
445
  const total = execute(_extends({}, state, {
418
446
  limit: null,
419
447
  after: state.after
@@ -427,6 +455,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
427
455
  }
428
456
  }),
429
457
  get: async ()=>{
458
+ readCount += 1;
430
459
  const docs = execute(state);
431
460
  return {
432
461
  docs,
@@ -445,8 +474,16 @@ export function queryFakeFirestore(seed = {}, options = {}) {
445
474
  after: null,
446
475
  limit: null
447
476
  });
448
- return _extends({}, base, {
449
- id: path.split('/').pop(),
477
+ const segments = path.split('/');
478
+ return _extends({
479
+ get parent () {
480
+ return segments.length > 1 ? docRef(segments.slice(0, -1).join('/')) : null;
481
+ },
482
+ get firestore () {
483
+ return firestore;
484
+ }
485
+ }, base, {
486
+ id: segments[segments.length - 1],
450
487
  path,
451
488
  doc: (id)=>docRef(`${path}/${id != null ? id : nextAutoId()}`),
452
489
  add: async (data)=>{
@@ -470,15 +507,19 @@ export function queryFakeFirestore(seed = {}, options = {}) {
470
507
  return snapshot(path);
471
508
  };
472
509
  const api = {
473
- get: async (target)=>typeof (target == null ? void 0 : target.path) === 'string' && target.path.split('/').length % 2 === 0 ? read(target.path) : target.get(),
474
- getAll: async (...refs)=>refs.map((ref)=>read(ref.path)),
510
+ get: async (target)=>{
511
+ if (typeof (target == null ? void 0 : target.path) === 'string' && target.path.split('/').length % 2 === 0) {
512
+ readCount += 1;
513
+ return read(target.path);
514
+ }
515
+ return target.get();
516
+ },
517
+ getAll: async (...refs)=>{
518
+ readCount += 1;
519
+ return refs.map((ref)=>read(ref.path));
520
+ },
475
521
  set: (ref, data, setOptions)=>{
476
- writes.push({
477
- type: 'set',
478
- path: ref.path,
479
- data,
480
- merge: (setOptions == null ? void 0 : setOptions.merge) === true
481
- });
522
+ writes.push(setWrite(ref.path, data, setOptions));
482
523
  return api;
483
524
  },
484
525
  update: (ref, data, precondition)=>{
@@ -537,7 +578,10 @@ export function queryFakeFirestore(seed = {}, options = {}) {
537
578
  const firestore = {
538
579
  collection: (path)=>collectionRef(path),
539
580
  doc: (path)=>docRef(path),
540
- getAll: async (...refs)=>refs.map((ref)=>snapshot(ref.path)),
581
+ getAll: async (...refs)=>{
582
+ readCount += 1;
583
+ return refs.map((ref)=>snapshot(ref.path));
584
+ },
541
585
  runTransaction: async (body)=>{
542
586
  for(let attempt = 0; attempt < 5; attempt += 1){
543
587
  const { writes, reads, api } = transactionOrBatch();
@@ -577,12 +621,7 @@ export function queryFakeFirestore(seed = {}, options = {}) {
577
621
  path: ref.path,
578
622
  data
579
623
  }),
580
- set: (ref, data, setOptions)=>enqueue({
581
- type: 'set',
582
- path: ref.path,
583
- data,
584
- merge: (setOptions == null ? void 0 : setOptions.merge) === true
585
- }),
624
+ set: (ref, data, setOptions)=>enqueue(setWrite(ref.path, data, setOptions)),
586
625
  update: (ref, data, precondition)=>enqueue({
587
626
  type: 'update',
588
627
  path: ref.path,
@@ -626,6 +665,10 @@ export function queryFakeFirestore(seed = {}, options = {}) {
626
665
  resetWrites: ()=>{
627
666
  writeCount = 0;
628
667
  },
668
+ reads: ()=>readCount,
669
+ resetReads: ()=>{
670
+ readCount = 0;
671
+ },
629
672
  setNow: (ms)=>{
630
673
  nowMs = ms;
631
674
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/test-firestore-queries.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A Firestore stand-in that QUERIES (AGL-3320). Test-only; deliberately not\n * exported from the barrel, like `test-firestore.ts` beside it.\n *\n * That fake holds documents and nothing else, which is enough for code that\n * reads a document by id. A consent group change pages whole subcollections\n * by document id, filters on nested map fields, counts, writes through a\n * `BulkWriter` with `lastUpdateTime` preconditions and takes transactions —\n * and its correctness properties (a second pass writes nothing, a resumed\n * pass skips nothing) are properties of exactly those mechanics, so a fake\n * that skipped them would be asserting itself.\n *\n * What is modeled, and is load-bearing:\n *\n * - A document missing a field never matches a filter or an ordering on it,\n * including `!=` — the rule that makes an `orderBy` on a data field drop\n * documents, and the reason every scan here pages by `__name__`.\n * - `update()` needs the document to exist, replaces the value at each\n * (dotted) path whole, and fails its `lastUpdateTime` precondition when\n * the document was written since; `create()` fails on an existing one.\n * The error codes are gRPC's: 5, 6, 9.\n * - `FieldValue` sentinels are applied, not stored: `delete`,\n * `serverTimestamp` (the fake clock), `increment`, `arrayUnion`,\n * `arrayRemove`.\n * - Every write moves `updateTime`, and a `BulkWriter` runs its queued\n * writes when it is flushed or closed, rejecting each failed write's\n * promise the way the real one does.\n * - A transaction is SERIALIZABLE: one whose reads changed before it\n * committed runs its body again, as the SDK retries a contended one, so\n * two transactions racing on one document cannot both act on what they\n * read. That is the property a start's \"no change in flight\" rests on.\n *\n * What is not: a `BulkWriter`'s own retries, which nothing here branches on.\n */\n\nimport { FieldPath, FieldValue, Timestamp } from 'firebase-admin/firestore'\n\ntype Data = Record<string, any>\n\ninterface Stored {\n data: Data\n createTime: Timestamp\n updateTime: Timestamp\n}\n\n/** A Firestore-style error: the gRPC code the SDK attaches. */\nfunction grpcError(code: number, message: string): Error {\n return Object.assign(new Error(message), { code })\n}\n\nconst isTransform = (value: unknown): value is FieldValue & { methodName: string } =>\n value instanceof FieldValue\n\nfunction clone<T>(value: T): T {\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp || value instanceof FieldValue) return value\n if (Array.isArray(value)) return value.map((item) => clone(item)) as T\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) out[key] = clone(item)\n return out as T\n}\n\n/** A field as a plain dotted path; a `FieldPath`'s segments are joined unquoted. */\nfunction fieldName(field: string | FieldPath): string {\n if (typeof field === 'string') return field\n const segments = (field as unknown as { segments?: string[] }).segments\n return Array.isArray(segments) ? segments.join('.') : String(field)\n}\n\n/** The value at a dotted path, or `undefined` when any segment is missing. */\nfunction valueAt(data: Data | undefined, path: string): unknown {\n let node: unknown = data\n for (const segment of path.split('.')) {\n if (!node || typeof node !== 'object' || Array.isArray(node)) return undefined\n node = (node as Data)[segment]\n }\n return node\n}\n\n/** Firestore's cross-type order: null, booleans, numbers, timestamps, strings, the rest. */\nfunction rank(value: unknown): number {\n if (value === null) return 0\n if (typeof value === 'boolean') return 1\n if (typeof value === 'number') return 2\n if (value instanceof Timestamp) return 3\n if (typeof value === 'string') return 4\n if (Array.isArray(value)) return 6\n return 7\n}\n\nfunction compare(a: unknown, b: unknown): number {\n const byRank = rank(a) - rank(b)\n if (byRank !== 0) return byRank\n if (a instanceof Timestamp && b instanceof Timestamp) {\n return a.seconds - b.seconds || a.nanoseconds - b.nanoseconds\n }\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (typeof a === 'boolean' && typeof b === 'boolean') return Number(a) - Number(b)\n const left = JSON.stringify(a)\n const right = JSON.stringify(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nconst equal = (a: unknown, b: unknown) => rank(a) === rank(b) && compare(a, b) === 0\n\ninterface Filter {\n field: string\n op: string\n value: unknown\n}\n\nfunction matches(id: string, data: Data, filter: Filter): boolean {\n const actual = filter.field === '__name__' ? id : valueAt(data, filter.field)\n if (actual === undefined) return false\n const { op, value } = filter\n switch (op) {\n case '==':\n return equal(actual, value)\n case '!=':\n return actual !== null && !equal(actual, value)\n case '<':\n return rank(actual) === rank(value) && compare(actual, value) < 0\n case '<=':\n return rank(actual) === rank(value) && compare(actual, value) <= 0\n case '>':\n return rank(actual) === rank(value) && compare(actual, value) > 0\n case '>=':\n return rank(actual) === rank(value) && compare(actual, value) >= 0\n case 'in':\n return (value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'not-in':\n return actual !== null && !(value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'array-contains':\n return Array.isArray(actual) && actual.some((item) => equal(item, value))\n case 'array-contains-any':\n return (\n Array.isArray(actual) &&\n actual.some((item) => (value as unknown[]).some((candidate) => equal(item, candidate)))\n )\n default:\n throw new Error(`test-firestore-queries: unsupported operator ${op}`)\n }\n}\n\nexport interface QueryFakeFirestore {\n collection(path: string): any\n doc(path: string): any\n getAll(...refs: any[]): Promise<any[]>\n runTransaction<T>(body: (transaction: any) => Promise<T>): Promise<T>\n batch(): any\n bulkWriter(): any\n /** Seed or overwrite one document, with no transforms and no write counted. */\n seed(path: string, data: Data): void\n /** One document's data as stored, or `undefined`. */\n read(path: string): Data | undefined\n /** Every document directly in a collection, by id. */\n docs(collectionPath: string): Record<string, Data>\n /** Committed writes since the last reset. */\n writes(): number\n resetWrites(): void\n /** The clock `serverTimestamp()` and every `updateTime` read. */\n setNow(ms: number): void\n /** Makes the next writes to `path` fail with `error` — a stand-in for an outage. */\n failWritesTo(path: string, error: Error | null): void\n}\n\nexport function queryFakeFirestore(\n seed: Record<string, Data> = {},\n options: { nowMs?: number } = {},\n): QueryFakeFirestore {\n const store = new Map<string, Stored>()\n let nowMs = options.nowMs ?? Date.UTC(2026, 8, 24)\n /** Keeps every write's `updateTime` distinct even on a frozen clock. */\n let tick = 0\n let writeCount = 0\n const failing = new Map<string, Error>()\n let autoIds = 0\n\n const stamp = () => Timestamp.fromMillis(nowMs + (tick += 0.001))\n const nextAutoId = () => {\n autoIds += 1\n return String(autoIds).padStart(8, '0')\n }\n\n for (const [path, data] of Object.entries(seed)) {\n const at = Timestamp.fromMillis(nowMs)\n store.set(path, { data: clone(data), createTime: at, updateTime: at })\n }\n\n /** Applies a write's value at one path of `target`, honoring transforms. */\n function put(target: Data, path: string[], value: unknown, previous: unknown): void {\n const [head, ...rest] = path\n if (rest.length) {\n const child = target[head]\n const next = child && typeof child === 'object' && !Array.isArray(child) ? child : {}\n target[head] = next\n put(next, rest, value, valueAt(previous as Data, rest.join('.')))\n return\n }\n if (isTransform(value)) {\n const current = target[head]\n switch (value.methodName) {\n case 'FieldValue.delete':\n delete target[head]\n return\n case 'FieldValue.serverTimestamp':\n target[head] = Timestamp.fromMillis(nowMs)\n return\n case 'FieldValue.increment': {\n const operand = (value as unknown as { operand: number }).operand\n target[head] = (typeof current === 'number' ? current : 0) + operand\n return\n }\n case 'FieldValue.arrayUnion': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n const base = Array.isArray(current) ? [...current] : []\n for (const element of elements) {\n if (!base.some((item) => equal(item, element))) base.push(element)\n }\n target[head] = base\n return\n }\n case 'FieldValue.arrayRemove': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n target[head] = (Array.isArray(current) ? current : []).filter(\n (item) => !elements.some((element) => equal(item, element)),\n )\n return\n }\n default:\n throw new Error(`test-firestore-queries: unsupported transform ${value.methodName}`)\n }\n }\n target[head] = materialize(value)\n }\n\n /** A written value with any nested transforms resolved against nothing. */\n function materialize(value: unknown): unknown {\n if (value === undefined) throw new Error('test-firestore-queries: undefined is not a Firestore value')\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp) return value\n if (isTransform(value)) {\n if (value.methodName === 'FieldValue.serverTimestamp') return Timestamp.fromMillis(nowMs)\n throw new Error(`test-firestore-queries: ${value.methodName} inside a nested value`)\n }\n if (Array.isArray(value)) return value.map((item) => materialize(item))\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) {\n if (isTransform(item) && item.methodName === 'FieldValue.delete') continue\n out[key] = materialize(item)\n }\n return out\n }\n\n function mergeInto(target: Data, source: Data): void {\n for (const [key, value] of Object.entries(source)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Timestamp) &&\n !isTransform(value)\n ) {\n const child = target[key]\n const next = child && typeof child === 'object' && !Array.isArray(child) && !(child instanceof Timestamp)\n ? child\n : {}\n target[key] = next\n mergeInto(next, value as Data)\n } else {\n put(target, [key], value, target[key])\n }\n }\n }\n\n type Write =\n | { type: 'set'; path: string; data: Data; merge: boolean }\n | { type: 'update'; path: string; data: Data; precondition?: { lastUpdateTime?: Timestamp } }\n | { type: 'create'; path: string; data: Data }\n | { type: 'delete'; path: string; precondition?: { lastUpdateTime?: Timestamp } }\n\n /** Why a write cannot apply, or `null`. */\n function refusal(write: Write): Error | null {\n const failure = failing.get(write.path)\n if (failure) return failure\n const existing = store.get(write.path)\n if (write.type === 'create' && existing) {\n return grpcError(6, `ALREADY_EXISTS: ${write.path}`)\n }\n if (write.type === 'update' && !existing) {\n return grpcError(5, `NOT_FOUND: ${write.path}`)\n }\n if ((write.type === 'update' || write.type === 'delete') && write.precondition?.lastUpdateTime) {\n if (!existing || !existing.updateTime.isEqual(write.precondition.lastUpdateTime)) {\n return grpcError(9, `FAILED_PRECONDITION: ${write.path}`)\n }\n }\n return null\n }\n\n function apply(write: Write): Timestamp {\n const existing = store.get(write.path)\n const at = stamp()\n writeCount += 1\n if (write.type === 'delete') {\n store.delete(write.path)\n return at\n }\n if (write.type === 'update') {\n const data = clone(existing?.data ?? {})\n for (const [path, value] of Object.entries(write.data)) {\n put(data, path.split('.'), value, valueAt(existing?.data, path))\n }\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n if (write.type === 'set' && write.merge) {\n const data = clone(existing?.data ?? {})\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n const data: Data = {}\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n\n function snapshot(path: string): any {\n const stored = store.get(path)\n const id = path.split('/').pop() as string\n return {\n id,\n ref: docRef(path),\n exists: stored !== undefined,\n data: () => (stored ? clone(stored.data) : undefined),\n get: (field: string | FieldPath) => clone(valueAt(stored?.data, fieldName(field))),\n updateTime: stored?.updateTime,\n createTime: stored?.createTime,\n }\n }\n\n function docRef(path: string): any {\n const segments = path.split('/')\n if (segments.length % 2 !== 0) throw new Error(`test-firestore-queries: ${path} is not a document path`)\n const ref: any = {\n id: segments[segments.length - 1],\n path,\n collection: (name: string) => collectionRef(`${path}/${name}`),\n get: async () => snapshot(path),\n set: async (data: Data, setOptions?: { merge?: boolean }) =>\n run({ type: 'set', path, data, merge: setOptions?.merge === true }),\n update: async (data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'update', path, data, precondition }),\n create: async (data: Data) => run({ type: 'create', path, data }),\n delete: async (precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'delete', path, precondition }),\n isEqual: (other: { path?: string }) => other?.path === path,\n }\n Object.defineProperty(ref, 'parent', {\n get: () => collectionRef(segments.slice(0, -1).join('/')),\n })\n return ref\n }\n\n function run(write: Write): { writeTime: Timestamp } {\n const failure = refusal(write)\n if (failure) throw failure\n return { writeTime: apply(write) }\n }\n\n interface QueryState {\n path: string\n filters: Filter[]\n orders: Array<{ field: string; direction: 'asc' | 'desc' }>\n after: unknown[] | null\n limit: number | null\n }\n\n function execute(state: QueryState): any[] {\n const prefix = `${state.path}/`\n let rows = [...store.entries()]\n .filter(([path]) => path.startsWith(prefix) && !path.slice(prefix.length).includes('/'))\n .map(([path, stored]) => ({ path, id: path.slice(prefix.length), data: stored.data }))\n .filter((row) => state.filters.every((filter) => matches(row.id, row.data, filter)))\n const orders = [...state.orders]\n if (!orders.some((order) => order.field === '__name__')) {\n orders.push({ field: '__name__', direction: 'asc' })\n }\n rows = rows.filter((row) =>\n orders.every((order) => order.field === '__name__' || valueAt(row.data, order.field) !== undefined),\n )\n const keyOf = (row: { id: string; data: Data }, field: string) =>\n field === '__name__' ? row.id : valueAt(row.data, field)\n rows.sort((a, b) => {\n for (const order of orders) {\n const result = compare(keyOf(a, order.field), keyOf(b, order.field))\n if (result !== 0) return order.direction === 'desc' ? -result : result\n }\n return 0\n })\n if (state.after) {\n const cursor = state.after\n rows = rows.filter((row) => {\n for (let index = 0; index < cursor.length; index += 1) {\n const order = orders[index]\n const result = compare(keyOf(row, order.field), cursor[index])\n const signed = order.direction === 'desc' ? -result : result\n if (signed !== 0) return signed > 0\n }\n return false\n })\n }\n if (state.limit !== null) rows = rows.slice(0, state.limit)\n return rows.map((row) => snapshot(row.path))\n }\n\n function query(state: QueryState): any {\n return {\n where: (field: string | FieldPath, op: string, value: unknown) =>\n query({ ...state, filters: [...state.filters, { field: fieldName(field), op, value }] }),\n orderBy: (field: string | FieldPath, direction: 'asc' | 'desc' = 'asc') =>\n query({ ...state, orders: [...state.orders, { field: fieldName(field), direction }] }),\n startAfter: (...values: unknown[]) => query({ ...state, after: values }),\n limit: (limit: number) => query({ ...state, limit }),\n count: () => ({\n get: async () => {\n const total = execute({ ...state, limit: null, after: state.after }).length\n const count = state.limit === null ? total : Math.min(total, state.limit)\n return { data: () => ({ count }) }\n },\n }),\n get: async () => {\n const docs = execute(state)\n return { docs, size: docs.length, empty: docs.length === 0, forEach: (fn: any) => docs.forEach(fn) }\n },\n }\n }\n\n function collectionRef(path: string): any {\n const base = query({ path, filters: [], orders: [], after: null, limit: null })\n return {\n ...base,\n id: path.split('/').pop(),\n path,\n doc: (id: string) => docRef(`${path}/${id ?? nextAutoId()}`),\n add: async (data: Data) => {\n const ref = docRef(`${path}/${nextAutoId()}`)\n run({ type: 'create', path: ref.path, data })\n return ref\n },\n }\n }\n\n function transactionOrBatch(): { writes: Write[]; reads: Map<string, Timestamp | null>; api: any } {\n const writes: Write[] = []\n const reads = new Map<string, Timestamp | null>()\n const read = (path: string) => {\n if (!reads.has(path)) reads.set(path, store.get(path)?.updateTime ?? null)\n return snapshot(path)\n }\n const api: any = {\n get: async (target: any) =>\n typeof target?.path === 'string' && target.path.split('/').length % 2 === 0\n ? read(target.path)\n : target.get(),\n getAll: async (...refs: any[]) => refs.map((ref) => read(ref.path)),\n set: (ref: any, data: Data, setOptions?: { merge?: boolean }) => {\n writes.push({ type: 'set', path: ref.path, data, merge: setOptions?.merge === true })\n return api\n },\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'update', path: ref.path, data, precondition })\n return api\n },\n create: (ref: any, data: Data) => {\n writes.push({ type: 'create', path: ref.path, data })\n return api\n },\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'delete', path: ref.path, precondition })\n return api\n },\n }\n return { writes, reads, api }\n }\n\n /** Whether every document a transaction read is still as it read it. */\n function unchanged(reads: Map<string, Timestamp | null>): boolean {\n for (const [path, seen] of reads) {\n const now = store.get(path)?.updateTime ?? null\n if (seen === null ? now !== null : now === null || !now.isEqual(seen)) return false\n }\n return true\n }\n\n /** All or nothing, in order: every write is checked against the state the earlier ones leave. */\n function commitAll(writes: Write[]): void {\n const saved = new Map(store)\n const count = writeCount\n try {\n for (const write of writes) run(write)\n } catch (error) {\n store.clear()\n for (const [path, stored] of saved) store.set(path, stored)\n writeCount = count\n throw error\n }\n }\n\n const firestore: QueryFakeFirestore = {\n collection: (path: string) => collectionRef(path),\n doc: (path: string) => docRef(path),\n getAll: async (...refs: any[]) => refs.map((ref) => snapshot(ref.path)),\n runTransaction: async (body) => {\n for (let attempt = 0; attempt < 5; attempt += 1) {\n const { writes, reads, api } = transactionOrBatch()\n const result = await body(api)\n if (!unchanged(reads)) continue\n commitAll(writes)\n return result\n }\n throw grpcError(10, 'ABORTED: too much contention')\n },\n batch: () => {\n const { writes, api } = transactionOrBatch()\n return { ...api, commit: async () => commitAll(writes) }\n },\n bulkWriter: () => {\n const queued: Array<{ write: Write; resolve: (value: unknown) => void; reject: (error: unknown) => void }> = []\n const enqueue = (write: Write) =>\n new Promise((resolve, reject) => queued.push({ write, resolve, reject }))\n const drain = async () => {\n while (queued.length) {\n const next = queued.shift() as (typeof queued)[number]\n try {\n next.resolve(run(next.write))\n } catch (error) {\n next.reject(error)\n }\n }\n }\n return {\n create: (ref: any, data: Data) => enqueue({ type: 'create', path: ref.path, data }),\n set: (ref: any, data: Data, setOptions?: { merge?: boolean }) =>\n enqueue({ type: 'set', path: ref.path, data, merge: setOptions?.merge === true }),\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'update', path: ref.path, data, precondition }),\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'delete', path: ref.path, precondition }),\n onWriteError: () => undefined,\n flush: drain,\n close: drain,\n }\n },\n seed: (path, data) => {\n const at = Timestamp.fromMillis(nowMs)\n const existing = store.get(path)\n store.set(path, { data: clone(data), createTime: existing?.createTime ?? at, updateTime: stamp() })\n },\n read: (path) => {\n const stored = store.get(path)\n return stored ? clone(stored.data) : undefined\n },\n docs: (collectionPath) => {\n const prefix = `${collectionPath}/`\n const out: Record<string, Data> = {}\n for (const [path, stored] of store) {\n const rest = path.slice(prefix.length)\n if (path.startsWith(prefix) && !rest.includes('/')) out[rest] = clone(stored.data)\n }\n return out\n },\n writes: () => writeCount,\n resetWrites: () => {\n writeCount = 0\n },\n setNow: (ms) => {\n nowMs = ms\n },\n failWritesTo: (path, error) => {\n if (error) failing.set(path, error)\n else failing.delete(path)\n },\n }\n return firestore\n}\n"],"names":["FieldValue","Timestamp","grpcError","code","message","Object","assign","Error","isTransform","value","clone","Array","isArray","map","item","out","key","entries","fieldName","field","segments","join","String","valueAt","data","path","node","segment","split","undefined","rank","compare","a","b","byRank","seconds","nanoseconds","Number","left","JSON","stringify","right","equal","matches","id","filter","actual","op","some","candidate","queryFakeFirestore","seed","options","store","Map","nowMs","Date","UTC","tick","writeCount","failing","autoIds","stamp","fromMillis","nextAutoId","padStart","at","set","createTime","updateTime","put","target","previous","head","rest","length","child","next","current","methodName","operand","elements","base","element","push","materialize","mergeInto","source","refusal","write","failure","get","existing","type","precondition","lastUpdateTime","isEqual","apply","delete","merge","snapshot","stored","pop","ref","docRef","exists","collection","name","collectionRef","setOptions","run","update","create","other","defineProperty","slice","writeTime","execute","state","prefix","rows","startsWith","includes","row","filters","every","orders","order","direction","keyOf","sort","result","after","cursor","index","signed","limit","query","where","orderBy","startAfter","values","count","total","Math","min","docs","size","empty","forEach","fn","doc","add","transactionOrBatch","writes","reads","read","has","api","getAll","refs","unchanged","seen","now","commitAll","saved","error","clear","firestore","runTransaction","body","attempt","batch","commit","bulkWriter","queued","enqueue","Promise","resolve","reject","drain","shift","onWriteError","flush","close","collectionPath","resetWrites","setNow","ms","failWritesTo"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/test-firestore-queries.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A Firestore stand-in that QUERIES (AGL-3320). Test-only; deliberately not\n * exported from the barrel, like `test-firestore.ts` beside it.\n *\n * That fake holds documents and nothing else, which is enough for code that\n * reads a document by id. A consent group change pages whole subcollections\n * by document id, filters on nested map fields, counts, writes through a\n * `BulkWriter` with `lastUpdateTime` preconditions and takes transactions —\n * and its correctness properties (a second pass writes nothing, a resumed\n * pass skips nothing) are properties of exactly those mechanics, so a fake\n * that skipped them would be asserting itself.\n *\n * What is modeled, and is load-bearing:\n *\n * - A document missing a field never matches a filter or an ordering on it,\n * including `!=` — the rule that makes an `orderBy` on a data field drop\n * documents, and the reason every scan here pages by `__name__`.\n * - `update()` needs the document to exist, replaces the value at each\n * (dotted) path whole, and fails its `lastUpdateTime` precondition when\n * the document was written since; `create()` fails on an existing one.\n * The error codes are gRPC's: 5, 6, 9. A `set()` with `mergeFields`\n * replaces the value at each named path whole, and creates the document\n * when it is missing.\n * - `FieldValue` sentinels are applied, not stored: `delete`,\n * `serverTimestamp` (the fake clock), `increment`, `arrayUnion`,\n * `arrayRemove`.\n * - Every write moves `updateTime`, and a `BulkWriter` runs its queued\n * writes when it is flushed or closed, rejecting each failed write's\n * promise the way the real one does.\n * - A transaction is SERIALIZABLE: one whose reads changed before it\n * committed runs its body again, as the SDK retries a contended one, so\n * two transactions racing on one document cannot both act on what they\n * read. That is the property a start's \"no change in flight\" rests on.\n *\n * What is not: a `BulkWriter`'s own retries, which nothing here branches on.\n */\n\nimport { FieldPath, FieldValue, Timestamp } from 'firebase-admin/firestore'\n\ntype Data = Record<string, any>\n\ninterface Stored {\n data: Data\n createTime: Timestamp\n updateTime: Timestamp\n}\n\n/** A Firestore-style error: the gRPC code the SDK attaches. */\nfunction grpcError(code: number, message: string): Error {\n return Object.assign(new Error(message), { code })\n}\n\nconst isTransform = (value: unknown): value is FieldValue & { methodName: string } =>\n value instanceof FieldValue\n\nfunction clone<T>(value: T): T {\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp || value instanceof FieldValue) return value\n if (Array.isArray(value)) return value.map((item) => clone(item)) as T\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) out[key] = clone(item)\n return out as T\n}\n\n/** A field as a plain dotted path; a `FieldPath`'s segments are joined unquoted. */\nfunction fieldName(field: string | FieldPath): string {\n if (typeof field === 'string') return field\n const segments = (field as unknown as { segments?: string[] }).segments\n return Array.isArray(segments) ? segments.join('.') : String(field)\n}\n\n/** The value at a dotted path, or `undefined` when any segment is missing. */\nfunction valueAt(data: Data | undefined, path: string): unknown {\n let node: unknown = data\n for (const segment of path.split('.')) {\n if (!node || typeof node !== 'object' || Array.isArray(node)) return undefined\n node = (node as Data)[segment]\n }\n return node\n}\n\n/** Firestore's cross-type order: null, booleans, numbers, timestamps, strings, the rest. */\nfunction rank(value: unknown): number {\n if (value === null) return 0\n if (typeof value === 'boolean') return 1\n if (typeof value === 'number') return 2\n if (value instanceof Timestamp) return 3\n if (typeof value === 'string') return 4\n if (Array.isArray(value)) return 6\n return 7\n}\n\nfunction compare(a: unknown, b: unknown): number {\n const byRank = rank(a) - rank(b)\n if (byRank !== 0) return byRank\n if (a instanceof Timestamp && b instanceof Timestamp) {\n return a.seconds - b.seconds || a.nanoseconds - b.nanoseconds\n }\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n if (typeof a === 'boolean' && typeof b === 'boolean') return Number(a) - Number(b)\n const left = JSON.stringify(a)\n const right = JSON.stringify(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\nconst equal = (a: unknown, b: unknown) => rank(a) === rank(b) && compare(a, b) === 0\n\ninterface Filter {\n field: string\n op: string\n value: unknown\n}\n\nfunction matches(id: string, data: Data, filter: Filter): boolean {\n const actual = filter.field === '__name__' ? id : valueAt(data, filter.field)\n if (actual === undefined) return false\n const { op, value } = filter\n switch (op) {\n case '==':\n return equal(actual, value)\n case '!=':\n return actual !== null && !equal(actual, value)\n case '<':\n return rank(actual) === rank(value) && compare(actual, value) < 0\n case '<=':\n return rank(actual) === rank(value) && compare(actual, value) <= 0\n case '>':\n return rank(actual) === rank(value) && compare(actual, value) > 0\n case '>=':\n return rank(actual) === rank(value) && compare(actual, value) >= 0\n case 'in':\n return (value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'not-in':\n return actual !== null && !(value as unknown[]).some((candidate) => equal(actual, candidate))\n case 'array-contains':\n return Array.isArray(actual) && actual.some((item) => equal(item, value))\n case 'array-contains-any':\n return (\n Array.isArray(actual) &&\n actual.some((item) => (value as unknown[]).some((candidate) => equal(item, candidate)))\n )\n default:\n throw new Error(`test-firestore-queries: unsupported operator ${op}`)\n }\n}\n\nexport interface QueryFakeFirestore {\n collection(path: string): any\n doc(path: string): any\n getAll(...refs: any[]): Promise<any[]>\n runTransaction<T>(body: (transaction: any) => Promise<T>): Promise<T>\n batch(): any\n bulkWriter(): any\n /** Seed or overwrite one document, with no transforms and no write counted. */\n seed(path: string, data: Data): void\n /** One document's data as stored, or `undefined`. */\n read(path: string): Data | undefined\n /** Every document directly in a collection, by id. */\n docs(collectionPath: string): Record<string, Data>\n /** Committed writes since the last reset. */\n writes(): number\n resetWrites(): void\n /**\n * Read ROUND TRIPS since the last reset: one per document `get`, query\n * `get`, `count()`, `getAll` or transaction read, however many documents\n * it returned — the unit a request's latency is spent in.\n */\n reads(): number\n resetReads(): void\n /** The clock `serverTimestamp()` and every `updateTime` read. */\n setNow(ms: number): void\n /** Makes the next writes to `path` fail with `error` — a stand-in for an outage. */\n failWritesTo(path: string, error: Error | null): void\n}\n\nexport function queryFakeFirestore(\n seed: Record<string, Data> = {},\n options: { nowMs?: number } = {},\n): QueryFakeFirestore {\n const store = new Map<string, Stored>()\n let nowMs = options.nowMs ?? Date.UTC(2026, 8, 24)\n /** Keeps every write's `updateTime` distinct even on a frozen clock. */\n let tick = 0\n let writeCount = 0\n let readCount = 0\n const failing = new Map<string, Error>()\n let autoIds = 0\n\n const stamp = () => Timestamp.fromMillis(nowMs + (tick += 0.001))\n const nextAutoId = () => {\n autoIds += 1\n return String(autoIds).padStart(8, '0')\n }\n\n for (const [path, data] of Object.entries(seed)) {\n const at = Timestamp.fromMillis(nowMs)\n store.set(path, { data: clone(data), createTime: at, updateTime: at })\n }\n\n /** Applies a write's value at one path of `target`, honoring transforms. */\n function put(target: Data, path: string[], value: unknown, previous: unknown): void {\n const [head, ...rest] = path\n if (rest.length) {\n const child = target[head]\n const next = child && typeof child === 'object' && !Array.isArray(child) ? child : {}\n target[head] = next\n put(next, rest, value, valueAt(previous as Data, rest.join('.')))\n return\n }\n if (isTransform(value)) {\n const current = target[head]\n switch (value.methodName) {\n case 'FieldValue.delete':\n delete target[head]\n return\n case 'FieldValue.serverTimestamp':\n target[head] = Timestamp.fromMillis(nowMs)\n return\n case 'FieldValue.increment': {\n const operand = (value as unknown as { operand: number }).operand\n target[head] = (typeof current === 'number' ? current : 0) + operand\n return\n }\n case 'FieldValue.arrayUnion': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n const base = Array.isArray(current) ? [...current] : []\n for (const element of elements) {\n if (!base.some((item) => equal(item, element))) base.push(element)\n }\n target[head] = base\n return\n }\n case 'FieldValue.arrayRemove': {\n const elements = (value as unknown as { elements: unknown[] }).elements\n target[head] = (Array.isArray(current) ? current : []).filter(\n (item) => !elements.some((element) => equal(item, element)),\n )\n return\n }\n default:\n throw new Error(`test-firestore-queries: unsupported transform ${value.methodName}`)\n }\n }\n target[head] = materialize(value)\n }\n\n /** A written value with any nested transforms resolved against nothing. */\n function materialize(value: unknown): unknown {\n if (value === undefined) throw new Error('test-firestore-queries: undefined is not a Firestore value')\n if (value === null || typeof value !== 'object') return value\n if (value instanceof Timestamp) return value\n if (isTransform(value)) {\n if (value.methodName === 'FieldValue.serverTimestamp') return Timestamp.fromMillis(nowMs)\n throw new Error(`test-firestore-queries: ${value.methodName} inside a nested value`)\n }\n if (Array.isArray(value)) return value.map((item) => materialize(item))\n const out: Data = {}\n for (const [key, item] of Object.entries(value as Data)) {\n if (isTransform(item) && item.methodName === 'FieldValue.delete') continue\n out[key] = materialize(item)\n }\n return out\n }\n\n function mergeInto(target: Data, source: Data): void {\n for (const [key, value] of Object.entries(source)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Timestamp) &&\n !isTransform(value)\n ) {\n const child = target[key]\n const next = child && typeof child === 'object' && !Array.isArray(child) && !(child instanceof Timestamp)\n ? child\n : {}\n target[key] = next\n mergeInto(next, value as Data)\n } else {\n put(target, [key], value, target[key])\n }\n }\n }\n\n type SetOptions = { merge?: boolean; mergeFields?: Array<string | FieldPath> }\n\n type Write =\n | { type: 'set'; path: string; data: Data; merge: boolean; fields?: string[] }\n | { type: 'update'; path: string; data: Data; precondition?: { lastUpdateTime?: Timestamp } }\n | { type: 'create'; path: string; data: Data }\n | { type: 'delete'; path: string; precondition?: { lastUpdateTime?: Timestamp } }\n\n /** Why a write cannot apply, or `null`. */\n function refusal(write: Write): Error | null {\n const failure = failing.get(write.path)\n if (failure) return failure\n const existing = store.get(write.path)\n if (write.type === 'create' && existing) {\n return grpcError(6, `ALREADY_EXISTS: ${write.path}`)\n }\n if (write.type === 'update' && !existing) {\n return grpcError(5, `NOT_FOUND: ${write.path}`)\n }\n if ((write.type === 'update' || write.type === 'delete') && write.precondition?.lastUpdateTime) {\n if (!existing || !existing.updateTime.isEqual(write.precondition.lastUpdateTime)) {\n return grpcError(9, `FAILED_PRECONDITION: ${write.path}`)\n }\n }\n return null\n }\n\n function apply(write: Write): Timestamp {\n const existing = store.get(write.path)\n const at = stamp()\n writeCount += 1\n if (write.type === 'delete') {\n store.delete(write.path)\n return at\n }\n if (write.type === 'update') {\n const data = clone(existing?.data ?? {})\n for (const [path, value] of Object.entries(write.data)) {\n put(data, path.split('.'), value, valueAt(existing?.data, path))\n }\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n if (write.type === 'set' && write.fields) {\n const data = clone(existing?.data ?? {})\n for (const path of write.fields) {\n put(data, path.split('.'), clone(valueAt(write.data, path)), valueAt(existing?.data, path))\n }\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n if (write.type === 'set' && write.merge) {\n const data = clone(existing?.data ?? {})\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n const data: Data = {}\n mergeInto(data, write.data)\n store.set(write.path, { data, createTime: existing?.createTime ?? at, updateTime: at })\n return at\n }\n\n function setWrite(path: string, data: Data, setOptions?: SetOptions): Write {\n return {\n type: 'set',\n path,\n data,\n merge: setOptions?.merge === true,\n ...(setOptions?.mergeFields ? { fields: setOptions.mergeFields.map(fieldName) } : {}),\n }\n }\n\n function snapshot(path: string): any {\n const stored = store.get(path)\n const id = path.split('/').pop() as string\n return {\n id,\n ref: docRef(path),\n exists: stored !== undefined,\n data: () => (stored ? clone(stored.data) : undefined),\n get: (field: string | FieldPath) => clone(valueAt(stored?.data, fieldName(field))),\n updateTime: stored?.updateTime,\n createTime: stored?.createTime,\n }\n }\n\n function docRef(path: string): any {\n const segments = path.split('/')\n if (segments.length % 2 !== 0) throw new Error(`test-firestore-queries: ${path} is not a document path`)\n const ref: any = {\n id: segments[segments.length - 1],\n path,\n collection: (name: string) => collectionRef(`${path}/${name}`),\n get: async () => {\n readCount += 1\n return snapshot(path)\n },\n set: async (data: Data, setOptions?: SetOptions) => run(setWrite(path, data, setOptions)),\n update: async (data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'update', path, data, precondition }),\n create: async (data: Data) => run({ type: 'create', path, data }),\n delete: async (precondition?: { lastUpdateTime?: Timestamp }) =>\n run({ type: 'delete', path, precondition }),\n isEqual: (other: { path?: string }) => other?.path === path,\n }\n Object.defineProperty(ref, 'parent', {\n get: () => collectionRef(segments.slice(0, -1).join('/')),\n })\n Object.defineProperty(ref, 'firestore', { get: () => firestore })\n return ref\n }\n\n function run(write: Write): { writeTime: Timestamp } {\n const failure = refusal(write)\n if (failure) throw failure\n return { writeTime: apply(write) }\n }\n\n interface QueryState {\n path: string\n filters: Filter[]\n orders: Array<{ field: string; direction: 'asc' | 'desc' }>\n after: unknown[] | null\n limit: number | null\n }\n\n function execute(state: QueryState): any[] {\n const prefix = `${state.path}/`\n let rows = [...store.entries()]\n .filter(([path]) => path.startsWith(prefix) && !path.slice(prefix.length).includes('/'))\n .map(([path, stored]) => ({ path, id: path.slice(prefix.length), data: stored.data }))\n .filter((row) => state.filters.every((filter) => matches(row.id, row.data, filter)))\n const orders = [...state.orders]\n if (!orders.some((order) => order.field === '__name__')) {\n orders.push({ field: '__name__', direction: 'asc' })\n }\n rows = rows.filter((row) =>\n orders.every((order) => order.field === '__name__' || valueAt(row.data, order.field) !== undefined),\n )\n const keyOf = (row: { id: string; data: Data }, field: string) =>\n field === '__name__' ? row.id : valueAt(row.data, field)\n rows.sort((a, b) => {\n for (const order of orders) {\n const result = compare(keyOf(a, order.field), keyOf(b, order.field))\n if (result !== 0) return order.direction === 'desc' ? -result : result\n }\n return 0\n })\n if (state.after) {\n const cursor = state.after\n rows = rows.filter((row) => {\n for (let index = 0; index < cursor.length; index += 1) {\n const order = orders[index]\n const result = compare(keyOf(row, order.field), cursor[index])\n const signed = order.direction === 'desc' ? -result : result\n if (signed !== 0) return signed > 0\n }\n return false\n })\n }\n if (state.limit !== null) rows = rows.slice(0, state.limit)\n return rows.map((row) => snapshot(row.path))\n }\n\n function query(state: QueryState): any {\n return {\n where: (field: string | FieldPath, op: string, value: unknown) =>\n query({ ...state, filters: [...state.filters, { field: fieldName(field), op, value }] }),\n orderBy: (field: string | FieldPath, direction: 'asc' | 'desc' = 'asc') =>\n query({ ...state, orders: [...state.orders, { field: fieldName(field), direction }] }),\n startAfter: (...values: unknown[]) => query({ ...state, after: values }),\n limit: (limit: number) => query({ ...state, limit }),\n count: () => ({\n get: async () => {\n readCount += 1\n const total = execute({ ...state, limit: null, after: state.after }).length\n const count = state.limit === null ? total : Math.min(total, state.limit)\n return { data: () => ({ count }) }\n },\n }),\n get: async () => {\n readCount += 1\n const docs = execute(state)\n return { docs, size: docs.length, empty: docs.length === 0, forEach: (fn: any) => docs.forEach(fn) }\n },\n }\n }\n\n function collectionRef(path: string): any {\n const base = query({ path, filters: [], orders: [], after: null, limit: null })\n const segments = path.split('/')\n return {\n get parent() {\n return segments.length > 1 ? docRef(segments.slice(0, -1).join('/')) : null\n },\n get firestore() {\n return firestore\n },\n ...base,\n id: segments[segments.length - 1],\n path,\n doc: (id: string) => docRef(`${path}/${id ?? nextAutoId()}`),\n add: async (data: Data) => {\n const ref = docRef(`${path}/${nextAutoId()}`)\n run({ type: 'create', path: ref.path, data })\n return ref\n },\n }\n }\n\n function transactionOrBatch(): { writes: Write[]; reads: Map<string, Timestamp | null>; api: any } {\n const writes: Write[] = []\n const reads = new Map<string, Timestamp | null>()\n const read = (path: string) => {\n if (!reads.has(path)) reads.set(path, store.get(path)?.updateTime ?? null)\n return snapshot(path)\n }\n const api: any = {\n get: async (target: any) => {\n if (typeof target?.path === 'string' && target.path.split('/').length % 2 === 0) {\n readCount += 1\n return read(target.path)\n }\n return target.get()\n },\n getAll: async (...refs: any[]) => {\n readCount += 1\n return refs.map((ref) => read(ref.path))\n },\n set: (ref: any, data: Data, setOptions?: SetOptions) => {\n writes.push(setWrite(ref.path, data, setOptions))\n return api\n },\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'update', path: ref.path, data, precondition })\n return api\n },\n create: (ref: any, data: Data) => {\n writes.push({ type: 'create', path: ref.path, data })\n return api\n },\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) => {\n writes.push({ type: 'delete', path: ref.path, precondition })\n return api\n },\n }\n return { writes, reads, api }\n }\n\n /** Whether every document a transaction read is still as it read it. */\n function unchanged(reads: Map<string, Timestamp | null>): boolean {\n for (const [path, seen] of reads) {\n const now = store.get(path)?.updateTime ?? null\n if (seen === null ? now !== null : now === null || !now.isEqual(seen)) return false\n }\n return true\n }\n\n /** All or nothing, in order: every write is checked against the state the earlier ones leave. */\n function commitAll(writes: Write[]): void {\n const saved = new Map(store)\n const count = writeCount\n try {\n for (const write of writes) run(write)\n } catch (error) {\n store.clear()\n for (const [path, stored] of saved) store.set(path, stored)\n writeCount = count\n throw error\n }\n }\n\n const firestore: QueryFakeFirestore = {\n collection: (path: string) => collectionRef(path),\n doc: (path: string) => docRef(path),\n getAll: async (...refs: any[]) => {\n readCount += 1\n return refs.map((ref) => snapshot(ref.path))\n },\n runTransaction: async (body) => {\n for (let attempt = 0; attempt < 5; attempt += 1) {\n const { writes, reads, api } = transactionOrBatch()\n const result = await body(api)\n if (!unchanged(reads)) continue\n commitAll(writes)\n return result\n }\n throw grpcError(10, 'ABORTED: too much contention')\n },\n batch: () => {\n const { writes, api } = transactionOrBatch()\n return { ...api, commit: async () => commitAll(writes) }\n },\n bulkWriter: () => {\n const queued: Array<{ write: Write; resolve: (value: unknown) => void; reject: (error: unknown) => void }> = []\n const enqueue = (write: Write) =>\n new Promise((resolve, reject) => queued.push({ write, resolve, reject }))\n const drain = async () => {\n while (queued.length) {\n const next = queued.shift() as (typeof queued)[number]\n try {\n next.resolve(run(next.write))\n } catch (error) {\n next.reject(error)\n }\n }\n }\n return {\n create: (ref: any, data: Data) => enqueue({ type: 'create', path: ref.path, data }),\n set: (ref: any, data: Data, setOptions?: SetOptions) => enqueue(setWrite(ref.path, data, setOptions)),\n update: (ref: any, data: Data, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'update', path: ref.path, data, precondition }),\n delete: (ref: any, precondition?: { lastUpdateTime?: Timestamp }) =>\n enqueue({ type: 'delete', path: ref.path, precondition }),\n onWriteError: () => undefined,\n flush: drain,\n close: drain,\n }\n },\n seed: (path, data) => {\n const at = Timestamp.fromMillis(nowMs)\n const existing = store.get(path)\n store.set(path, { data: clone(data), createTime: existing?.createTime ?? at, updateTime: stamp() })\n },\n read: (path) => {\n const stored = store.get(path)\n return stored ? clone(stored.data) : undefined\n },\n docs: (collectionPath) => {\n const prefix = `${collectionPath}/`\n const out: Record<string, Data> = {}\n for (const [path, stored] of store) {\n const rest = path.slice(prefix.length)\n if (path.startsWith(prefix) && !rest.includes('/')) out[rest] = clone(stored.data)\n }\n return out\n },\n writes: () => writeCount,\n resetWrites: () => {\n writeCount = 0\n },\n reads: () => readCount,\n resetReads: () => {\n readCount = 0\n },\n setNow: (ms) => {\n nowMs = ms\n },\n failWritesTo: (path, error) => {\n if (error) failing.set(path, error)\n else failing.delete(path)\n },\n }\n return firestore\n}\n"],"names":["FieldValue","Timestamp","grpcError","code","message","Object","assign","Error","isTransform","value","clone","Array","isArray","map","item","out","key","entries","fieldName","field","segments","join","String","valueAt","data","path","node","segment","split","undefined","rank","compare","a","b","byRank","seconds","nanoseconds","Number","left","JSON","stringify","right","equal","matches","id","filter","actual","op","some","candidate","queryFakeFirestore","seed","options","store","Map","nowMs","Date","UTC","tick","writeCount","readCount","failing","autoIds","stamp","fromMillis","nextAutoId","padStart","at","set","createTime","updateTime","put","target","previous","head","rest","length","child","next","current","methodName","operand","elements","base","element","push","materialize","mergeInto","source","refusal","write","failure","get","existing","type","precondition","lastUpdateTime","isEqual","apply","delete","fields","merge","setWrite","setOptions","mergeFields","snapshot","stored","pop","ref","docRef","exists","collection","name","collectionRef","run","update","create","other","defineProperty","slice","firestore","writeTime","execute","state","prefix","rows","startsWith","includes","row","filters","every","orders","order","direction","keyOf","sort","result","after","cursor","index","signed","limit","query","where","orderBy","startAfter","values","count","total","Math","min","docs","size","empty","forEach","fn","parent","doc","add","transactionOrBatch","writes","reads","read","has","api","getAll","refs","unchanged","seen","now","commitAll","saved","error","clear","runTransaction","body","attempt","batch","commit","bulkWriter","queued","enqueue","Promise","resolve","reject","drain","shift","onWriteError","flush","close","collectionPath","resetWrites","resetReads","setNow","ms","failWritesTo"],"mappings":"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"}