@rudderhq/server 0.7.24-canary.2 → 0.7.24-canary.21
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.
- package/dist/agent-runtimes/registry.d.ts.map +1 -1
- package/dist/agent-runtimes/registry.js +3 -17
- package/dist/agent-runtimes/registry.js.map +1 -1
- package/dist/agent-runtimes/registry.test.d.ts +2 -0
- package/dist/agent-runtimes/registry.test.d.ts.map +1 -0
- package/dist/agent-runtimes/registry.test.js +25 -0
- package/dist/agent-runtimes/registry.test.js.map +1 -0
- package/dist/app.d.ts +9 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +20 -0
- package/dist/app.js.map +1 -1
- package/dist/bootstrap/create-http-app.d.ts +2 -0
- package/dist/bootstrap/create-http-app.d.ts.map +1 -1
- package/dist/bootstrap/create-http-app.js +57 -2
- package/dist/bootstrap/create-http-app.js.map +1 -1
- package/dist/bootstrap/public-ingress-startup.d.ts +12 -0
- package/dist/bootstrap/public-ingress-startup.d.ts.map +1 -0
- package/dist/bootstrap/public-ingress-startup.js +54 -0
- package/dist/bootstrap/public-ingress-startup.js.map +1 -0
- package/dist/bootstrap/register-api-routes.d.ts +2 -1
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +5 -5
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bootstrap/types.d.ts +14 -0
- package/dist/bootstrap/types.d.ts.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -1
- package/dist/embedded-postgres-startup-logs.d.ts +6 -0
- package/dist/embedded-postgres-startup-logs.d.ts.map +1 -0
- package/dist/embedded-postgres-startup-logs.js +48 -0
- package/dist/embedded-postgres-startup-logs.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -30
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +10 -2
- package/dist/middleware/auth.js.map +1 -1
- package/dist/realtime/live-events-ws.d.ts.map +1 -1
- package/dist/realtime/live-events-ws.js +14 -0
- package/dist/realtime/live-events-ws.js.map +1 -1
- package/dist/routes/agents.d.ts +2 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +2 -10
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/agents.management-routes.d.ts.map +1 -1
- package/dist/routes/agents.management-routes.js +16 -60
- package/dist/routes/agents.management-routes.js.map +1 -1
- package/dist/routes/onboarding.d.ts +2 -1
- package/dist/routes/onboarding.d.ts.map +1 -1
- package/dist/routes/onboarding.js +50 -17
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/routes/organization-rust-foundation-routes.d.ts +21 -0
- package/dist/routes/organization-rust-foundation-routes.d.ts.map +1 -0
- package/dist/routes/organization-rust-foundation-routes.js +410 -0
- package/dist/routes/organization-rust-foundation-routes.js.map +1 -0
- package/dist/routes/orgs.d.ts +4 -1
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +38 -102
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/projects.d.ts +2 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +301 -28
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/public-ingress-auth.d.ts +8 -0
- package/dist/routes/public-ingress-auth.d.ts.map +1 -0
- package/dist/routes/public-ingress-auth.js +144 -0
- package/dist/routes/public-ingress-auth.js.map +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +0 -1
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agent-runtime-availability.d.ts.map +1 -1
- package/dist/services/agent-runtime-availability.js +0 -3
- package/dist/services/agent-runtime-availability.js.map +1 -1
- package/dist/services/assets.d.ts.map +1 -1
- package/dist/services/assets.js +9 -5
- package/dist/services/assets.js.map +1 -1
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +21 -19
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +1 -1
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
- package/dist/services/chat-assistant.runtime-overrides.d.ts.map +1 -1
- package/dist/services/chat-assistant.runtime-overrides.js +0 -2
- package/dist/services/chat-assistant.runtime-overrides.js.map +1 -1
- package/dist/services/chats.annotation-persistence.d.ts.map +1 -1
- package/dist/services/chats.annotation-persistence.js +2 -0
- package/dist/services/chats.annotation-persistence.js.map +1 -1
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.helpers.d.ts +2 -0
- package/dist/services/chats.helpers.d.ts.map +1 -1
- package/dist/services/chats.helpers.js +17 -1
- package/dist/services/chats.helpers.js.map +1 -1
- package/dist/services/chats.js +17 -33
- package/dist/services/chats.js.map +1 -1
- package/dist/services/chats.queued-message-create.d.ts.map +1 -1
- package/dist/services/chats.queued-message-create.js +2 -0
- package/dist/services/chats.queued-message-create.js.map +1 -1
- package/dist/services/chats.user-state-initialization.d.ts +5 -0
- package/dist/services/chats.user-state-initialization.d.ts.map +1 -0
- package/dist/services/chats.user-state-initialization.js +27 -0
- package/dist/services/chats.user-state-initialization.js.map +1 -0
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +2 -0
- package/dist/services/costs.js.map +1 -1
- package/dist/services/goal-public-contract.d.ts +13 -0
- package/dist/services/goal-public-contract.d.ts.map +1 -0
- package/dist/services/goal-public-contract.js +106 -0
- package/dist/services/goal-public-contract.js.map +1 -0
- package/dist/services/goal-public-contract.test.d.ts +2 -0
- package/dist/services/goal-public-contract.test.d.ts.map +1 -0
- package/dist/services/goal-public-contract.test.js +45 -0
- package/dist/services/goal-public-contract.test.js.map +1 -0
- package/dist/services/goals.d.ts +1 -2
- package/dist/services/goals.d.ts.map +1 -1
- package/dist/services/goals.js +72 -137
- package/dist/services/goals.js.map +1 -1
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/issues.comments-attachments.d.ts.map +1 -1
- package/dist/services/issues.comments-attachments.js +3 -0
- package/dist/services/issues.comments-attachments.js.map +1 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.helpers.d.ts +3 -0
- package/dist/services/issues.helpers.d.ts.map +1 -1
- package/dist/services/issues.helpers.js +23 -0
- package/dist/services/issues.helpers.js.map +1 -1
- package/dist/services/issues.js +12 -24
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.js +0 -4
- package/dist/services/knowledge-portability/organization-portability.core.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.d.ts +6 -2
- package/dist/services/knowledge-portability/organization-portability.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.import.d.ts +5 -1
- package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.import.js +92 -9
- package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.js +3 -1
- package/dist/services/knowledge-portability/organization-portability.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js +0 -6
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
- package/dist/services/live-events.d.ts +2 -0
- package/dist/services/live-events.d.ts.map +1 -1
- package/dist/services/live-events.js +7 -1
- package/dist/services/live-events.js.map +1 -1
- package/dist/services/live-run-read-bridge.d.ts +11 -0
- package/dist/services/live-run-read-bridge.d.ts.map +1 -0
- package/dist/services/live-run-read-bridge.js +16 -0
- package/dist/services/live-run-read-bridge.js.map +1 -0
- package/dist/services/live-run-read-bridge.test.d.ts +2 -0
- package/dist/services/live-run-read-bridge.test.d.ts.map +1 -0
- package/dist/services/live-run-read-bridge.test.js +99 -0
- package/dist/services/live-run-read-bridge.test.js.map +1 -0
- package/dist/services/migration-preflight.d.ts.map +1 -1
- package/dist/services/migration-preflight.js +1 -0
- package/dist/services/migration-preflight.js.map +1 -1
- package/dist/services/migration-preflight.test.js +24 -0
- package/dist/services/migration-preflight.test.js.map +1 -1
- package/dist/services/organization-branding-fence.d.ts +30 -0
- package/dist/services/organization-branding-fence.d.ts.map +1 -0
- package/dist/services/organization-branding-fence.js +99 -0
- package/dist/services/organization-branding-fence.js.map +1 -0
- package/dist/services/organization-branding-fence.test.d.ts +2 -0
- package/dist/services/organization-branding-fence.test.d.ts.map +1 -0
- package/dist/services/organization-branding-fence.test.js +44 -0
- package/dist/services/organization-branding-fence.test.js.map +1 -0
- package/dist/services/organization-intelligence-profile-defaults.d.ts.map +1 -1
- package/dist/services/organization-intelligence-profile-defaults.js +32 -16
- package/dist/services/organization-intelligence-profile-defaults.js.map +1 -1
- package/dist/services/organization-intelligence-profiles.d.ts +1 -1
- package/dist/services/organization-intelligence-profiles.d.ts.map +1 -1
- package/dist/services/organization-intelligence-profiles.js +3 -2
- package/dist/services/organization-intelligence-profiles.js.map +1 -1
- package/dist/services/organization-mutation-fence.d.ts +21 -0
- package/dist/services/organization-mutation-fence.d.ts.map +1 -0
- package/dist/services/organization-mutation-fence.js +40 -0
- package/dist/services/organization-mutation-fence.js.map +1 -0
- package/dist/services/organization-mutation-outbox.d.ts +22 -0
- package/dist/services/organization-mutation-outbox.d.ts.map +1 -0
- package/dist/services/organization-mutation-outbox.js +125 -0
- package/dist/services/organization-mutation-outbox.js.map +1 -0
- package/dist/services/organization-mutation-outbox.test.d.ts +2 -0
- package/dist/services/organization-mutation-outbox.test.d.ts.map +1 -0
- package/dist/services/organization-mutation-outbox.test.js +80 -0
- package/dist/services/organization-mutation-outbox.test.js.map +1 -0
- package/dist/services/organization-resource-mutation-fence.d.ts +29 -0
- package/dist/services/organization-resource-mutation-fence.d.ts.map +1 -0
- package/dist/services/organization-resource-mutation-fence.js +75 -0
- package/dist/services/organization-resource-mutation-fence.js.map +1 -0
- package/dist/services/organization-resource-rust-authority.d.ts +7 -0
- package/dist/services/organization-resource-rust-authority.d.ts.map +1 -0
- package/dist/services/organization-resource-rust-authority.js +38 -0
- package/dist/services/organization-resource-rust-authority.js.map +1 -0
- package/dist/services/organization-resource-rust-authority.test.d.ts +2 -0
- package/dist/services/organization-resource-rust-authority.test.d.ts.map +1 -0
- package/dist/services/organization-resource-rust-authority.test.js +48 -0
- package/dist/services/organization-resource-rust-authority.test.js.map +1 -0
- package/dist/services/orgs.d.ts.map +1 -1
- package/dist/services/orgs.js +20 -2
- package/dist/services/orgs.js.map +1 -1
- package/dist/services/product-analytics.d.ts +1 -1
- package/dist/services/project-goal-mutation-fence.d.ts +35 -0
- package/dist/services/project-goal-mutation-fence.d.ts.map +1 -0
- package/dist/services/project-goal-mutation-fence.js +277 -0
- package/dist/services/project-goal-mutation-fence.js.map +1 -0
- package/dist/services/project-goal-mutation-fence.test.d.ts +2 -0
- package/dist/services/project-goal-mutation-fence.test.d.ts.map +1 -0
- package/dist/services/project-goal-mutation-fence.test.js +278 -0
- package/dist/services/project-goal-mutation-fence.test.js.map +1 -0
- package/dist/services/projects-create.test.d.ts +2 -0
- package/dist/services/projects-create.test.d.ts.map +1 -0
- package/dist/services/projects-create.test.js +174 -0
- package/dist/services/projects-create.test.js.map +1 -0
- package/dist/services/projects.d.ts +19 -3
- package/dist/services/projects.d.ts.map +1 -1
- package/dist/services/projects.js +215 -57
- package/dist/services/projects.js.map +1 -1
- package/dist/services/resource-catalog.d.ts.map +1 -1
- package/dist/services/resource-catalog.js +188 -112
- package/dist/services/resource-catalog.js.map +1 -1
- package/dist/services/run-intelligence.d.ts.map +1 -1
- package/dist/services/run-intelligence.js +3 -1
- package/dist/services/run-intelligence.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +0 -1
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.js +8 -4
- package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js +2 -0
- package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
- package/dist/services/rust-foundation-bridge.d.ts +95 -0
- package/dist/services/rust-foundation-bridge.d.ts.map +1 -0
- package/dist/services/rust-foundation-bridge.js +802 -0
- package/dist/services/rust-foundation-bridge.js.map +1 -0
- package/dist/services/rust-foundation-bridge.test.d.ts +2 -0
- package/dist/services/rust-foundation-bridge.test.d.ts.map +1 -0
- package/dist/services/rust-foundation-bridge.test.js +829 -0
- package/dist/services/rust-foundation-bridge.test.js.map +1 -0
- package/dist/services/side-chats.d.ts.map +1 -1
- package/dist/services/side-chats.js +38 -19
- package/dist/services/side-chats.js.map +1 -1
- package/package.json +19 -16
- package/resources/bundled-skills/rudder-docs/references/cli-reference.md +2 -1
- package/resources/bundled-skills/visualize/SKILL.md +1 -1
- package/resources/native/aarch64-apple-darwin/migration-preflight +0 -0
- package/resources/native/aarch64-apple-darwin/rudder-server-foundation +0 -0
- package/resources/native/aarch64-pc-windows-msvc/migration-preflight.exe +0 -0
- package/resources/native/aarch64-pc-windows-msvc/rudder-server-foundation.exe +0 -0
- package/resources/native/aarch64-unknown-linux-gnu/migration-preflight +0 -0
- package/resources/native/aarch64-unknown-linux-gnu/rudder-server-foundation +0 -0
- package/resources/native/x86_64-apple-darwin/migration-preflight +0 -0
- package/resources/native/x86_64-apple-darwin/rudder-server-foundation +0 -0
- package/resources/native/x86_64-pc-windows-msvc/migration-preflight.exe +0 -0
- package/resources/native/x86_64-pc-windows-msvc/rudder-server-foundation.exe +0 -0
- package/resources/native/x86_64-unknown-linux-gnu/migration-preflight +0 -0
- package/resources/native/x86_64-unknown-linux-gnu/rudder-server-foundation +0 -0
- package/resources/postinstall-embedded-postgres.mjs +193 -0
- package/resources/postinstall-native-mode.mjs +36 -0
- package/skills/rudder-docs/references/cli-reference.md +2 -1
- package/skills/visualize/SKILL.md +1 -1
- package/ui-dist/assets/{LibraryLiveSurface-BpUD1odP.js → LibraryLiveSurface-Bs8V_pMU.js} +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BPrBDdld.js → abnfDiagram-VRR7QNED-DneqWru9.js} +1 -1
- package/ui-dist/assets/{addon-fit-DNQtwoFo.js → addon-fit-DCRNyXYv.js} +1 -1
- package/ui-dist/assets/{arc-IXEjKK3z.js → arc-D_2I_R5Z.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-D5-GsWPu.js → architectureDiagram-ZJ3FMSHR-C0ANAv78.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-BT91oUno.js → blockDiagram-677ZJIJ3-BqFGOix5.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-DZa4R-RY.js → c4Diagram-LMCZKHZV-82LfEi1P.js} +1 -1
- package/ui-dist/assets/channel-DjvWm5jR.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-BJn1mzn0.js → chunk-2Q5K7J3B-DijvvGcl.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-DX4CN1No.js → chunk-32BRIVSS-Z09s337w.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-Bvq0Cx67.js → chunk-5VM5RSS4-DgQWkO6B.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-C9g5iHPO.js → chunk-EX3LRPZG-UTEQqeds.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-DoPFngr_.js → chunk-JWPE2WC7-DAaSu1vD.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-CZeNor1c.js → chunk-MOJQB5TN-D_fTXJM3.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-DSIa1gN4.js → chunk-RYQCIY6F-C7H_axhC.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-B-t9NovP.js → chunk-V7JOEXUC-B2toa05O.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-C17_to2o.js → chunk-VR4S4FIN-CkRvPkE2.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CMKjuqZI.js → chunk-XXDRQBXY-DOO_r0VK.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CNSRItW2.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CNSRItW2.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC--JzUailr.js → cose-bilkent-JH36ORCC-BzYjE_M-.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-Is3IDXR5.js → cynefin-VYW2F7L2-Dcf0eQJ5.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-D9pZPkr_.js → cynefinDiagram-TSTJHNR4-Cs_Lv8Ua.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DQtVOPbA.js → dagre-VKFMJZFB-B8oZME7g.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-nzceu0kB.js → diagram-FQU43EPY-DFHkB-_t.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-Dw-D5CH5.js → diagram-G47NLZAW-FGxRm1A-.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-CoyPw9Uo.js → diagram-NH7WQ7WH-Ot83ScOb.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Cqoi9a6k.js → diagram-OA4YK3LP-CkiHNUvw.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-D8RWrHSh.js → diagram-WEI45ONY-CxM3g_L3.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-By9B5HMb.js → ebnfDiagram-CCIWWBDH-DbcqsHcY.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-rwMzFibF.js → erDiagram-Q63AITRT-DLROlxDN.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BaCpiX-T.js → flowDiagram-23GEKE2U-DphM_YLM.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-DzXDtk7b.js → ganttDiagram-NO4QXBWP--QIRtWYP.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-C1RILXL1.js → gitGraphDiagram-IHSO6WYX-Bg3QwNvE.js} +1 -1
- package/ui-dist/assets/{graph-RzQsSMN0.js → graph-B_1y14m_.js} +1 -1
- package/ui-dist/assets/{index-DeIqk5X9.js → index-6O1Ef9oA.js} +1 -1
- package/ui-dist/assets/{index-Ufqpd6-1.js → index-Aj3Mi7R6.js} +1 -1
- package/ui-dist/assets/{index-C9N1nzkG.js → index-B-yeMW3t.js} +1 -1
- package/ui-dist/assets/{index-Dp0HIAhW.js → index-BABow2kh.js} +1 -1
- package/ui-dist/assets/{index-DUw2q_Tn.js → index-BEgTumxB.js} +1 -1
- package/ui-dist/assets/{index-QzfB2Hm8.js → index-BQheb_6L.js} +1 -1
- package/ui-dist/assets/index-B_tEQoXz.css +32 -0
- package/ui-dist/assets/{index-S_I6m7qi.js → index-Bi9I1TFJ.js} +561 -560
- package/ui-dist/assets/{index-1CcOBUAh.js → index-BlFoD6Gg.js} +1 -1
- package/ui-dist/assets/{index-Bj8AKsm3.js → index-BueQLOw6.js} +1 -1
- package/ui-dist/assets/{index-CTY5MHTT.js → index-CMUgKK2j.js} +1 -1
- package/ui-dist/assets/{index-DLz28cdj.js → index-CS2TW9nf.js} +1 -1
- package/ui-dist/assets/{index-B7vWk3jX.js → index-CcWXJMSa.js} +1 -1
- package/ui-dist/assets/{index-Cm40qa2n.js → index-CdAtz86o.js} +1 -1
- package/ui-dist/assets/{index-DobP_OA5.js → index-E2v-0TkN.js} +1 -1
- package/ui-dist/assets/{index-Cd1EZEkn.js → index-XWYyP2hU.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-DVIzbK0n.js → infoDiagram-FWYZ7A6U-BxygKDVp.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BoCZRszc.js → ishikawaDiagram-FXEZZL3T-47zucfG2.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-DCRkA_lv.js → journeyDiagram-5HDEW3XC-D8cyI8aY.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-sttcO1jJ.js → kanban-definition-HUTT4EX6-CrxRNese.js} +1 -1
- package/ui-dist/assets/{layout-u47UnAg9.js → layout-OyZo5VN3.js} +1 -1
- package/ui-dist/assets/{linear-Hx-0ykqm.js → linear-CuiwlwQB.js} +1 -1
- package/ui-dist/assets/{map-JHAuHxTf.js → map-rixHzQ5d.js} +1 -1
- package/ui-dist/assets/{mermaid.core-duAMtZT0.js → mermaid.core-BBG92fxH.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BqImMmHB.js → mindmap-definition-LN4V7U3C-CivqbhCV.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-BBqSimCZ.js → pegDiagram-2B236MQR-JrPSWk9k.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-IvsJkrmf.js → pieDiagram-ENE6RG2P-F4HEWR5n.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CBW_UfNv.js → quadrantDiagram-ABIIQ3AL-BJNly8dn.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-Zjn4XqMZ.js → railroadDiagram-RFXS5EU6-DNORDnb2.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DPk09fAM.js → requirementDiagram-TGXJPOKE-710xtGEU.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-qzaGYd61.js → sankeyDiagram-HTMAVEWB-BmWJHmHA.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-B-4BDFMx.js → sequenceDiagram-DBY2YBRQ-A73TFc3V.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-S3KZiokK.js → sizeCapture-X5ZJPWSS-B8otrPI2.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-BumYuIdY.js → stateDiagram-2N3HPSRC-BZ9k729q.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DepHJpk7.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-T_bbOzkS.js → swimlanes-5IMT3BWC-W_CH_z9Z.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BRL-ddud.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-95nLoqCi.js → timeline-definition-FHXFAJF6-CcXbC6J1.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-DROzWeg9.js → vennDiagram-L72KCM5P-BMHmsQqg.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-f6rn8OB5.js → wardleyDiagram-EHGQE667-Bq2-CEvU.js} +1 -1
- package/ui-dist/assets/{xterm-DOe84fOC.js → xterm-8vrqRuWm.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-JdlcTNzG.js → xychartDiagram-FW5EYKEG-De3RUAME.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-GV4x9fGv.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-C9tnRpuJ.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-C9tnRpuJ.js +0 -1
- package/ui-dist/assets/index-3zlQxsgk.css +0 -32
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-Cmr2JA9o.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BnoZ05uR.js +0 -8
- package/ui-dist/brands/google-gemini-logo.svg +0 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { LIVE_EVENT_TYPES } from "@rudderhq/shared";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { logger } from "../middleware/logger.js";
|
|
4
|
+
import { publishLiveEvent } from "./live-events.js";
|
|
5
|
+
const DEFAULT_INTERVAL_MS = 1_000;
|
|
6
|
+
const DEFAULT_BATCH_SIZE = 50;
|
|
7
|
+
const MAX_ERROR_LENGTH = 2_000;
|
|
8
|
+
function errorText(error) {
|
|
9
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10
|
+
return message.slice(0, MAX_ERROR_LENGTH);
|
|
11
|
+
}
|
|
12
|
+
function isLiveEventType(value) {
|
|
13
|
+
return LIVE_EVENT_TYPES.includes(value);
|
|
14
|
+
}
|
|
15
|
+
async function claimBatch(db, batchSize) {
|
|
16
|
+
const result = await db.execute(sql `
|
|
17
|
+
WITH claim AS (
|
|
18
|
+
SELECT id
|
|
19
|
+
FROM organization_mutation_outbox
|
|
20
|
+
WHERE state = 'pending'
|
|
21
|
+
AND next_attempt_at <= now()
|
|
22
|
+
ORDER BY next_attempt_at ASC, created_at ASC, id ASC
|
|
23
|
+
FOR UPDATE SKIP LOCKED
|
|
24
|
+
LIMIT ${batchSize}
|
|
25
|
+
)
|
|
26
|
+
UPDATE organization_mutation_outbox AS outbox
|
|
27
|
+
SET attempts = outbox.attempts + 1,
|
|
28
|
+
next_attempt_at = now() + interval '1 second'
|
|
29
|
+
FROM claim
|
|
30
|
+
WHERE outbox.id = claim.id
|
|
31
|
+
RETURNING outbox.id::text,
|
|
32
|
+
outbox.org_id::text,
|
|
33
|
+
outbox.event_type,
|
|
34
|
+
outbox.payload,
|
|
35
|
+
outbox.attempts
|
|
36
|
+
`);
|
|
37
|
+
const postgresResult = result;
|
|
38
|
+
return Array.isArray(postgresResult)
|
|
39
|
+
? postgresResult
|
|
40
|
+
: (postgresResult.rows ?? []);
|
|
41
|
+
}
|
|
42
|
+
async function markPublished(db, row) {
|
|
43
|
+
await db.execute(sql `
|
|
44
|
+
UPDATE organization_mutation_outbox
|
|
45
|
+
SET state = 'published', published_at = now(), last_error = NULL
|
|
46
|
+
WHERE id = ${row.id}::uuid
|
|
47
|
+
AND state = 'pending'
|
|
48
|
+
AND attempts = ${row.attempts}
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
async function markFailed(db, row, error) {
|
|
52
|
+
await db.execute(sql `
|
|
53
|
+
UPDATE organization_mutation_outbox
|
|
54
|
+
SET last_error = ${errorText(error)},
|
|
55
|
+
next_attempt_at = now() + make_interval(
|
|
56
|
+
secs => LEAST(60::double precision, GREATEST(1::double precision,
|
|
57
|
+
power(2::double precision, LEAST(attempts, 6)::double precision)))
|
|
58
|
+
)
|
|
59
|
+
WHERE id = ${row.id}::uuid
|
|
60
|
+
AND state = 'pending'
|
|
61
|
+
AND attempts = ${row.attempts}
|
|
62
|
+
`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Deliver Rust activity events through the transitional Node event bus.
|
|
66
|
+
*
|
|
67
|
+
* Reasoning:
|
|
68
|
+
* - claim leases are persisted before publication, so a process crash leaves
|
|
69
|
+
* the row eligible for retry instead of losing the event;
|
|
70
|
+
* - publication is completed before the row becomes published, so the Rust
|
|
71
|
+
* business transaction never relies on an in-process Node side effect;
|
|
72
|
+
* - the service is intentionally scoped to the current outbox table and can
|
|
73
|
+
* be removed when the Rust websocket/event transport owns delivery.
|
|
74
|
+
*/
|
|
75
|
+
export function startOrganizationMutationOutboxPublisher(db, options = {}) {
|
|
76
|
+
const intervalMs = Math.max(100, options.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
77
|
+
const batchSize = Math.max(1, Math.min(500, options.batchSize ?? DEFAULT_BATCH_SIZE));
|
|
78
|
+
let closed = false;
|
|
79
|
+
let drainInFlight = null;
|
|
80
|
+
const drain = async () => {
|
|
81
|
+
if (closed || drainInFlight)
|
|
82
|
+
return drainInFlight;
|
|
83
|
+
drainInFlight = (async () => {
|
|
84
|
+
const rows = await claimBatch(db, batchSize);
|
|
85
|
+
for (const row of rows) {
|
|
86
|
+
try {
|
|
87
|
+
if (!isLiveEventType(row.event_type)) {
|
|
88
|
+
throw new Error(`Unsupported organization mutation outbox event type: ${row.event_type}`);
|
|
89
|
+
}
|
|
90
|
+
publishLiveEvent({
|
|
91
|
+
orgId: row.org_id,
|
|
92
|
+
type: row.event_type,
|
|
93
|
+
payload: row.payload,
|
|
94
|
+
dedupeKey: `organization-mutation-outbox:${row.id}`,
|
|
95
|
+
});
|
|
96
|
+
await markPublished(db, row);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
await markFailed(db, row, error);
|
|
100
|
+
logger.warn({ err: error, outboxId: row.id, orgId: row.org_id }, "organization mutation outbox delivery failed");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})().finally(() => {
|
|
104
|
+
drainInFlight = null;
|
|
105
|
+
});
|
|
106
|
+
return drainInFlight;
|
|
107
|
+
};
|
|
108
|
+
const timer = setInterval(() => {
|
|
109
|
+
void drain().catch((error) => {
|
|
110
|
+
logger.warn({ err: error }, "organization mutation outbox claim failed");
|
|
111
|
+
});
|
|
112
|
+
}, intervalMs);
|
|
113
|
+
void drain().catch((error) => {
|
|
114
|
+
logger.warn({ err: error }, "organization mutation outbox startup drain failed");
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
drain,
|
|
118
|
+
async close() {
|
|
119
|
+
closed = true;
|
|
120
|
+
clearInterval(timer);
|
|
121
|
+
await drainInFlight;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=organization-mutation-outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-mutation-outbox.js","sourceRoot":"","sources":["../../src/services/organization-mutation-outbox.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAsB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAClC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAe/B,SAAS,SAAS,CAAC,KAAc;IAC/B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,OAAQ,gBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,EAAM,EAAE,SAAiB;IACjD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;;;;;;cAQvB,SAAS;;;;;;;;;;;;GAYpB,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,MAEtB,CAAC;IACF,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;QAClC,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,EAAM,EAAE,GAAqB;IACxD,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;iBAGL,GAAG,CAAC,EAAE;;uBAEA,GAAG,CAAC,QAAQ;GAChC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,EAAM,EAAE,GAAqB,EAAE,KAAc;IACrE,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;uBAEC,SAAS,CAAC,KAAK,CAAC;;;;;iBAKtB,GAAG,CAAC,EAAE;;uBAEA,GAAG,CAAC,QAAQ;GAChC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wCAAwC,CACtD,EAAM,EACN,UAAkC,EAAE;IAEpC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAC,CAAC;IACtF,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,aAAa,GAAyB,IAAI,CAAC;IAE/C,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;QACvB,IAAI,MAAM,IAAI,aAAa;YAAE,OAAO,aAAa,CAAC;QAClD,aAAa,GAAG,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;YAC7C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACH,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;wBACrC,MAAM,IAAI,KAAK,CAAC,wDAAwD,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;oBAC5F,CAAC;oBACD,gBAAgB,CAAC;wBACf,KAAK,EAAE,GAAG,CAAC,MAAM;wBACjB,IAAI,EAAE,GAAG,CAAC,UAAU;wBACpB,OAAO,EAAE,GAAG,CAAC,OAAO;wBACpB,SAAS,EAAE,gCAAgC,GAAG,CAAC,EAAE,EAAE;qBACpD,CAAC,CAAC;oBACH,MAAM,aAAa,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBAC/B,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,UAAU,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;oBACjC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,8CAA8C,CAAC,CAAC;gBACnH,CAAC;YACH,CAAC;QACH,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAChB,aAAa,GAAG,IAAI,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,OAAO,aAAa,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,2CAA2C,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,UAAU,CAAC,CAAC;IAEf,KAAK,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,mDAAmD,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,KAAK;QACL,KAAK,CAAC,KAAK;YACT,MAAM,GAAG,IAAI,CAAC;YACd,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,aAAa,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-mutation-outbox.test.d.ts","sourceRoot":"","sources":["../../src/services/organization-mutation-outbox.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const publishLiveEvent = vi.hoisted(() => vi.fn());
|
|
3
|
+
vi.mock("./live-events.js", () => ({
|
|
4
|
+
publishLiveEvent,
|
|
5
|
+
}));
|
|
6
|
+
import { startOrganizationMutationOutboxPublisher } from "./organization-mutation-outbox.js";
|
|
7
|
+
const ROW = {
|
|
8
|
+
id: "00000000-0000-4000-8000-000000000001",
|
|
9
|
+
org_id: "00000000-0000-4000-8000-000000000002",
|
|
10
|
+
event_type: "activity.logged",
|
|
11
|
+
payload: { action: "organization.branding_updated" },
|
|
12
|
+
attempts: 1,
|
|
13
|
+
};
|
|
14
|
+
function createDb(rows, resultShape = "rows") {
|
|
15
|
+
const execute = vi.fn()
|
|
16
|
+
.mockResolvedValueOnce(resultShape === "rows" ? { rows } : rows)
|
|
17
|
+
.mockResolvedValue({ rows: [] });
|
|
18
|
+
return {
|
|
19
|
+
db: { execute },
|
|
20
|
+
execute,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
describe("organization mutation outbox publisher", () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
publishLiveEvent.mockReset();
|
|
26
|
+
});
|
|
27
|
+
it("publishes a claimed activity and acknowledges it", async () => {
|
|
28
|
+
const { db, execute } = createDb([ROW]);
|
|
29
|
+
const publisher = startOrganizationMutationOutboxPublisher(db, { intervalMs: 60_000 });
|
|
30
|
+
await publisher.drain();
|
|
31
|
+
await publisher.close();
|
|
32
|
+
expect(publishLiveEvent).toHaveBeenCalledWith({
|
|
33
|
+
orgId: ROW.org_id,
|
|
34
|
+
type: ROW.event_type,
|
|
35
|
+
payload: ROW.payload,
|
|
36
|
+
dedupeKey: `organization-mutation-outbox:${ROW.id}`,
|
|
37
|
+
});
|
|
38
|
+
expect(execute).toHaveBeenCalledTimes(2);
|
|
39
|
+
});
|
|
40
|
+
it("accepts the array result returned by the postgres-js Drizzle driver", async () => {
|
|
41
|
+
const { db, execute } = createDb([ROW], "array");
|
|
42
|
+
const publisher = startOrganizationMutationOutboxPublisher(db, { intervalMs: 60_000 });
|
|
43
|
+
await publisher.drain();
|
|
44
|
+
await publisher.close();
|
|
45
|
+
expect(publishLiveEvent).toHaveBeenCalledWith({
|
|
46
|
+
orgId: ROW.org_id,
|
|
47
|
+
type: ROW.event_type,
|
|
48
|
+
payload: ROW.payload,
|
|
49
|
+
dedupeKey: `organization-mutation-outbox:${ROW.id}`,
|
|
50
|
+
});
|
|
51
|
+
expect(execute).toHaveBeenCalledTimes(2);
|
|
52
|
+
});
|
|
53
|
+
it("keeps a failed or unknown event retryable and records the failure", async () => {
|
|
54
|
+
const { db, execute } = createDb([
|
|
55
|
+
{ ...ROW, event_type: "unsupported.event" },
|
|
56
|
+
]);
|
|
57
|
+
const publisher = startOrganizationMutationOutboxPublisher(db, { intervalMs: 60_000 });
|
|
58
|
+
await publisher.drain();
|
|
59
|
+
await publisher.close();
|
|
60
|
+
expect(publishLiveEvent).not.toHaveBeenCalled();
|
|
61
|
+
expect(execute).toHaveBeenCalledTimes(2);
|
|
62
|
+
const retryQuery = JSON.stringify(execute.mock.calls[1]?.[0]);
|
|
63
|
+
expect(retryQuery).toContain("power");
|
|
64
|
+
expect(retryQuery).toContain("last_error");
|
|
65
|
+
});
|
|
66
|
+
it("records a publication exception without acknowledging the row", async () => {
|
|
67
|
+
const { db, execute } = createDb([ROW]);
|
|
68
|
+
publishLiveEvent.mockImplementationOnce(() => {
|
|
69
|
+
throw new Error("websocket unavailable");
|
|
70
|
+
});
|
|
71
|
+
const publisher = startOrganizationMutationOutboxPublisher(db, { intervalMs: 60_000 });
|
|
72
|
+
await publisher.drain();
|
|
73
|
+
await publisher.close();
|
|
74
|
+
expect(execute).toHaveBeenCalledTimes(2);
|
|
75
|
+
const retryQuery = JSON.stringify(execute.mock.calls[1]?.[0]);
|
|
76
|
+
expect(retryQuery).toContain("last_error");
|
|
77
|
+
expect(retryQuery).not.toContain("published_at");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=organization-mutation-outbox.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-mutation-outbox.test.js","sourceRoot":"","sources":["../../src/services/organization-mutation-outbox.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type TransactionClient = {
|
|
2
|
+
execute(query: unknown): Promise<unknown>;
|
|
3
|
+
};
|
|
4
|
+
type OrganizationResourceMutationStateRow = {
|
|
5
|
+
resource_id: string;
|
|
6
|
+
org_id: string;
|
|
7
|
+
owner: string;
|
|
8
|
+
mutation_version: string | number | bigint;
|
|
9
|
+
fence_epoch: string | number | bigint;
|
|
10
|
+
fence_token: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Lock resource authority after the caller has locked the organization row.
|
|
14
|
+
*
|
|
15
|
+
* Reasoning:
|
|
16
|
+
* - Existing canonical rows predate this fence, so provision only a missing
|
|
17
|
+
* row whose canonical organization matches the caller's organization.
|
|
18
|
+
* - When a canonical row was already scoped to this organization, read the
|
|
19
|
+
* component row by resource ID to detect a corrupt org boundary. Tombstone
|
|
20
|
+
* lookups stay org-scoped so a foreign ID remains indistinguishable from a
|
|
21
|
+
* missing resource.
|
|
22
|
+
* - No resource foreign key is used: deletion leaves this row as a replay
|
|
23
|
+
* tombstone.
|
|
24
|
+
*/
|
|
25
|
+
export declare function lockNodeOrganizationResourceMutationAuthority(tx: TransactionClient, organizationId: string, resourceId: string, options?: {
|
|
26
|
+
initializeFromCanonical?: boolean;
|
|
27
|
+
}): Promise<OrganizationResourceMutationStateRow | null>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=organization-resource-mutation-fence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-resource-mutation-fence.d.ts","sourceRoot":"","sources":["../../src/services/organization-resource-mutation-fence.ts"],"names":[],"mappings":"AAGA,KAAK,iBAAiB,GAAG;IACvB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3C,CAAC;AAEF,KAAK,oCAAoC,GAAG;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC3C,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AASF;;;;;;;;;;;;GAYG;AACH,wBAAsB,6CAA6C,CACjE,EAAE,EAAE,iBAAiB,EACrB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAO,GAClD,OAAO,CAAC,oCAAoC,GAAG,IAAI,CAAC,CAsDtD"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { conflict } from "../errors.js";
|
|
3
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
+
function firstRow(result) {
|
|
5
|
+
if (Array.isArray(result))
|
|
6
|
+
return result[0];
|
|
7
|
+
return result.rows?.[0];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Lock resource authority after the caller has locked the organization row.
|
|
11
|
+
*
|
|
12
|
+
* Reasoning:
|
|
13
|
+
* - Existing canonical rows predate this fence, so provision only a missing
|
|
14
|
+
* row whose canonical organization matches the caller's organization.
|
|
15
|
+
* - When a canonical row was already scoped to this organization, read the
|
|
16
|
+
* component row by resource ID to detect a corrupt org boundary. Tombstone
|
|
17
|
+
* lookups stay org-scoped so a foreign ID remains indistinguishable from a
|
|
18
|
+
* missing resource.
|
|
19
|
+
* - No resource foreign key is used: deletion leaves this row as a replay
|
|
20
|
+
* tombstone.
|
|
21
|
+
*/
|
|
22
|
+
export async function lockNodeOrganizationResourceMutationAuthority(tx, organizationId, resourceId, options = {}) {
|
|
23
|
+
const initializeFromCanonical = options.initializeFromCanonical ?? false;
|
|
24
|
+
const readLockedState = async () => firstRow(await tx.execute(sql `
|
|
25
|
+
SELECT resource_id::text AS resource_id,
|
|
26
|
+
org_id::text AS org_id,
|
|
27
|
+
owner,
|
|
28
|
+
mutation_version,
|
|
29
|
+
fence_epoch,
|
|
30
|
+
fence_token::text AS fence_token
|
|
31
|
+
FROM organization_resource_mutation_state
|
|
32
|
+
WHERE resource_id = ${resourceId}::uuid
|
|
33
|
+
${initializeFromCanonical ? sql `` : sql `AND org_id = ${organizationId}::uuid`}
|
|
34
|
+
FOR UPDATE
|
|
35
|
+
`));
|
|
36
|
+
// Reject an existing Rust owner (including a tombstone) without issuing DML.
|
|
37
|
+
// The caller holds the organization lock throughout lookup/provisioning.
|
|
38
|
+
let row = await readLockedState();
|
|
39
|
+
if (!row && initializeFromCanonical) {
|
|
40
|
+
await tx.execute(sql `
|
|
41
|
+
INSERT INTO organization_resource_mutation_state (resource_id, org_id)
|
|
42
|
+
SELECT id, org_id
|
|
43
|
+
FROM organization_resources
|
|
44
|
+
WHERE id = ${resourceId}::uuid AND org_id = ${organizationId}::uuid
|
|
45
|
+
ON CONFLICT (resource_id) DO NOTHING
|
|
46
|
+
`);
|
|
47
|
+
row = await readLockedState();
|
|
48
|
+
}
|
|
49
|
+
if (!row) {
|
|
50
|
+
if (initializeFromCanonical) {
|
|
51
|
+
throw conflict("Organization resource mutation authority is not provisioned");
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (row.resource_id.toLowerCase() !== resourceId.toLowerCase()
|
|
56
|
+
|| row.org_id.toLowerCase() !== organizationId.toLowerCase()) {
|
|
57
|
+
throw conflict("Organization resource mutation authority has an invalid scope");
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
if (BigInt(row.mutation_version) < 0n || BigInt(row.fence_epoch) < 0n) {
|
|
61
|
+
throw new Error("negative resource mutation fence counter");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw conflict("Organization resource mutation fence counters are invalid");
|
|
66
|
+
}
|
|
67
|
+
if (!UUID_PATTERN.test(row.fence_token)) {
|
|
68
|
+
throw conflict("Organization resource mutation fencing token is invalid");
|
|
69
|
+
}
|
|
70
|
+
if (row.owner !== "node") {
|
|
71
|
+
throw conflict("Organization resource mutation authority is owned by Rust");
|
|
72
|
+
}
|
|
73
|
+
return row;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=organization-resource-mutation-fence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-resource-mutation-fence.js","sourceRoot":"","sources":["../../src/services/organization-resource-mutation-fence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAexC,MAAM,YAAY,GAAG,4EAA4E,CAAC;AAElG,SAAS,QAAQ,CAAC,MAAe;IAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAqD,CAAC;IAChG,OAAQ,MAA4D,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACjF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,6CAA6C,CACjE,EAAqB,EACrB,cAAsB,EACtB,UAAkB,EAClB,UAAiD,EAAE;IAEnD,MAAM,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,IAAI,KAAK,CAAC;IACzE,MAAM,eAAe,GAAG,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;;;;;;0BAQzC,UAAU;QAC5B,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAA,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,gBAAgB,cAAc,QAAQ;;GAEhF,CAAC,CAAC,CAAC;IACJ,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,GAAG,GAAG,MAAM,eAAe,EAAE,CAAC;IAClC,IAAI,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACpC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;;mBAIL,UAAU,uBAAuB,cAAc;;KAE7D,CAAC,CAAC;QACH,GAAG,GAAG,MAAM,eAAe,EAAE,CAAC;IAChC,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,uBAAuB,EAAE,CAAC;YAC5B,MAAM,QAAQ,CAAC,6DAA6D,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IACE,GAAG,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,WAAW,EAAE;WACvD,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,WAAW,EAAE,EAC5D,CAAC;QACD,MAAM,QAAQ,CAAC,+DAA+D,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,QAAQ,CAAC,2DAA2D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,MAAM,QAAQ,CAAC,yDAAyD,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QACzB,MAAM,QAAQ,CAAC,2DAA2D,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Db } from "@rudderhq/db";
|
|
2
|
+
import type { Request } from "express";
|
|
3
|
+
import type { RustFoundationBridge, RustFoundationResponse } from "./rust-foundation-bridge.js";
|
|
4
|
+
/** Routing is advisory; both writers repeat ownership checks under the shared
|
|
5
|
+
* organization mutex. A rejected Rust invocation must never select Node. */
|
|
6
|
+
export declare function forwardRustOrganizationResourceMutation(db: Db, bridge: RustFoundationBridge | undefined, req: Request, orgId: string, resourceId: string, operation: "update" | "delete", data: Record<string, unknown>): Promise<RustFoundationResponse | null>;
|
|
7
|
+
//# sourceMappingURL=organization-resource-rust-authority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-resource-rust-authority.d.ts","sourceRoot":"","sources":["../../src/services/organization-resource-rust-authority.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAEhG;4EAC4E;AAC5E,wBAAsB,uCAAuC,CAC3D,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,oBAAoB,GAAG,SAAS,EACxC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CA6BxC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { HttpError } from "../errors.js";
|
|
4
|
+
/** Routing is advisory; both writers repeat ownership checks under the shared
|
|
5
|
+
* organization mutex. A rejected Rust invocation must never select Node. */
|
|
6
|
+
export async function forwardRustOrganizationResourceMutation(db, bridge, req, orgId, resourceId, operation, data) {
|
|
7
|
+
const result = await db.execute(sql `
|
|
8
|
+
SELECT (
|
|
9
|
+
EXISTS (
|
|
10
|
+
SELECT 1 FROM organization_resource_mutation_state
|
|
11
|
+
WHERE org_id = ${orgId}::uuid AND resource_id = ${resourceId}::uuid AND owner = 'rust'
|
|
12
|
+
) OR EXISTS (
|
|
13
|
+
SELECT 1 FROM project_resource_attachments a
|
|
14
|
+
JOIN project_goal_mutation_state p ON p.project_id = a.project_id AND p.org_id = a.org_id
|
|
15
|
+
WHERE a.org_id = ${orgId}::uuid AND a.resource_id = ${resourceId}::uuid AND p.owner = 'rust'
|
|
16
|
+
)
|
|
17
|
+
) AS requires_rust
|
|
18
|
+
`);
|
|
19
|
+
const rows = Array.isArray(result) ? result : result.rows ?? [];
|
|
20
|
+
const row = rows[0];
|
|
21
|
+
if (row?.requires_rust === false)
|
|
22
|
+
return null;
|
|
23
|
+
if (row?.requires_rust !== true) {
|
|
24
|
+
throw new HttpError(503, "Organization resource authority could not be verified");
|
|
25
|
+
}
|
|
26
|
+
if (bridge?.projectGoalSetMode !== "required" || !bridge.organizationResourceMutation) {
|
|
27
|
+
throw new HttpError(503, "Rust organization resource authority is unavailable");
|
|
28
|
+
}
|
|
29
|
+
const key = req.header("x-rudder-idempotency-key")?.trim() || randomUUID();
|
|
30
|
+
const body = Buffer.from(JSON.stringify({ data, runId: req.actor.runId ?? null }), "utf8");
|
|
31
|
+
try {
|
|
32
|
+
return await bridge.organizationResourceMutation(req, orgId, resourceId, operation, body, key);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new HttpError(503, "Rust organization resource authority is unavailable");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=organization-resource-rust-authority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-resource-rust-authority.js","sourceRoot":"","sources":["../../src/services/organization-resource-rust-authority.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC;4EAC4E;AAC5E,MAAM,CAAC,KAAK,UAAU,uCAAuC,CAC3D,EAAM,EACN,MAAwC,EACxC,GAAY,EACZ,KAAa,EACb,UAAkB,EAClB,SAA8B,EAC9B,IAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;;yBAIZ,KAAK,4BAA4B,UAAU;;;;2BAIzC,KAAK,8BAA8B,UAAU;;;GAGrE,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,MAA+B,CAAC,IAAI,IAAI,EAAE,CAAC;IAC1F,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAA4C,CAAC;IAC/D,IAAI,GAAG,EAAE,aAAa,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,GAAG,EAAE,aAAa,KAAK,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,uDAAuD,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,MAAM,EAAE,kBAAkB,KAAK,UAAU,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC;QACtF,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,qDAAqD,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,0BAA0B,CAAC,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC;IAC3E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3F,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,4BAA4B,CAAC,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACjG,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,qDAAqD,CAAC,CAAC;IAClF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-resource-rust-authority.test.d.ts","sourceRoot":"","sources":["../../src/services/organization-resource-rust-authority.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { forwardRustOrganizationResourceMutation } from "./organization-resource-rust-authority.js";
|
|
3
|
+
const orgId = "11111111-1111-4111-8111-111111111111";
|
|
4
|
+
const resourceId = "22222222-2222-4222-8222-222222222222";
|
|
5
|
+
const req = {
|
|
6
|
+
actor: { type: "board", source: "local_implicit", userId: "local-board" },
|
|
7
|
+
header: (name) => name === "x-rudder-idempotency-key" ? "resource-retry" : undefined,
|
|
8
|
+
};
|
|
9
|
+
function fixture(rows) {
|
|
10
|
+
const execute = vi.fn().mockResolvedValue(rows);
|
|
11
|
+
const mutation = vi.fn();
|
|
12
|
+
const bridge = {
|
|
13
|
+
projectGoalSetMode: "required",
|
|
14
|
+
organizationResourceMutation: mutation,
|
|
15
|
+
};
|
|
16
|
+
return { db: { execute }, bridge, mutation };
|
|
17
|
+
}
|
|
18
|
+
describe("organization resource authority routing", () => {
|
|
19
|
+
it("leaves a verified Node-only resource on its existing authority", async () => {
|
|
20
|
+
const { db, bridge, mutation } = fixture([{ requires_rust: false }]);
|
|
21
|
+
expect(await forwardRustOrganizationResourceMutation(db, bridge, req, orgId, resourceId, "update", { name: "Shared" })).toBeNull();
|
|
22
|
+
expect(mutation).not.toHaveBeenCalled();
|
|
23
|
+
});
|
|
24
|
+
it("fails closed when resource ownership cannot be established", async () => {
|
|
25
|
+
const { db, bridge, mutation } = fixture([]);
|
|
26
|
+
await expect(forwardRustOrganizationResourceMutation(db, bridge, req, orgId, resourceId, "delete", {}))
|
|
27
|
+
.rejects.toMatchObject({ status: 503 });
|
|
28
|
+
expect(mutation).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
it("keeps a Rust outage terminal instead of selecting the Node writer", async () => {
|
|
31
|
+
const { db, bridge, mutation } = fixture([{ requires_rust: true }]);
|
|
32
|
+
mutation.mockRejectedValue(new Error("connection lost after possible commit"));
|
|
33
|
+
await expect(forwardRustOrganizationResourceMutation(db, bridge, req, orgId, resourceId, "update", { name: "Retry" }))
|
|
34
|
+
.rejects.toMatchObject({ status: 503 });
|
|
35
|
+
expect(mutation).toHaveBeenCalledOnce();
|
|
36
|
+
});
|
|
37
|
+
it("preserves the stable key, operation and exact Rust failure response", async () => {
|
|
38
|
+
const { db, bridge, mutation } = fixture([{ requires_rust: true }]);
|
|
39
|
+
const response = { status: 409, contentType: "application/json", body: Buffer.from('{"error":"mutation_idempotency_conflict"}') };
|
|
40
|
+
mutation.mockResolvedValue(response);
|
|
41
|
+
expect(await forwardRustOrganizationResourceMutation(db, bridge, req, orgId, resourceId, "update", { name: "Changed" })).toBe(response);
|
|
42
|
+
const args = mutation.mock.calls[0];
|
|
43
|
+
expect(args.slice(0, 4)).toEqual([req, orgId, resourceId, "update"]);
|
|
44
|
+
expect(JSON.parse(args[4].toString("utf8"))).toEqual({ data: { name: "Changed" }, runId: null });
|
|
45
|
+
expect(args[5]).toBe("resource-retry");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=organization-resource-rust-authority.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"organization-resource-rust-authority.test.js","sourceRoot":"","sources":["../../src/services/organization-resource-rust-authority.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/services/orgs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,
|
|
1
|
+
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/services/orgs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EA0CL,aAAa,EAUd,MAAM,cAAc,CAAC;AAmBtB,KAAK,wBAAwB,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC/E,KAAK,uBAAuB,GAAG,OAAO,aAAa,CAAC,YAAY,GAAG;IACjE,SAAS,CAAC,EAAE;QACV,YAAY,CAAC,EAAE,wBAAwB,CAAC;QACxC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,CAAC;CACH,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwMlB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;mBAaX,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;iBAgEhC,MAAM,QACJ,OAAO,CAAC,OAAO,aAAa,CAAC,YAAY,CAAC,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;;;kBAoHtE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBP,MAAM;;;;;;;;;;;;;;;;;;;;oBA8E4B,MAAM;oBAAc,MAAM;;EAc5E"}
|
package/dist/services/orgs.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { activityLog, agentApiKeys, agentConfigRevisions, agentRuntimeState, agents, agentTaskSessions, agentWakeupRequests, approvalComments, approvals, assets, budgetIncidents, budgetPolicies, chatAttachments, chatContextLinks, chatConversations, chatMessages, costEvents, costMonthlySpendRollups, documentRevisions, documents, executionWorkspaces, financeEvents, goals, heartbeatRunEvents, heartbeatRuns, invites, issueApprovals, issueAttachments, issueBlockAuditAttempts, issueComments, issueDocuments, issueReadStates, issues, issueWorkProducts, joinRequests, labels, organizationIssuePrefixAliases, organizationLogos, organizationMemberships, organizations, organizationSecrets, organizationSkills, principalPermissionGrants, projectGoals, projects, projectWorkspaces, requests, workspaceOperations, workspaceRuntimeServices, } from "@rudderhq/db";
|
|
1
|
+
import { activityLog, agentApiKeys, agentConfigRevisions, agentRuntimeState, agents, agentTaskSessions, agentWakeupRequests, approvalComments, approvals, assets, budgetIncidents, budgetPolicies, chatAttachments, chatContextLinks, chatConversations, chatMessages, costEvents, costMonthlySpendRollups, documentRevisions, documents, executionWorkspaces, financeEvents, goals, heartbeatRunEvents, heartbeatRuns, invites, issueApprovals, issueAttachments, issueBlockAuditAttempts, issueComments, issueDocuments, issueReadStates, issues, issueWorkProducts, joinRequests, labels, organizationBrandingMutationState, organizationIssuePrefixAliases, organizationLogos, organizationMemberships, organizationMutationState, organizations, organizationSecrets, organizationSkills, principalPermissionGrants, projectGoals, projects, projectWorkspaces, requests, workspaceOperations, workspaceRuntimeServices, } from "@rudderhq/db";
|
|
2
2
|
import { deriveOrganizationIssueKey, deriveOrganizationUrlKey, normalizeOrganizationIssueKey, } from "@rudderhq/shared";
|
|
3
3
|
import { and, count, eq, gte, inArray, lt, sql } from "drizzle-orm";
|
|
4
4
|
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
5
5
|
import { ensureOrganizationWorkspaceLayout, removeOrganizationStorage } from "../home-paths.js";
|
|
6
6
|
import { logger } from "../middleware/logger.js";
|
|
7
|
+
import { lockNodeOrganizationBrandingAuthority, lockOrganizationBrandingAuthorityForDeletion, } from "./organization-branding-fence.js";
|
|
8
|
+
import { lockNodeMutationAuthority } from "./organization-mutation-fence.js";
|
|
7
9
|
import { isPostgresError } from "./postgres-errors.js";
|
|
8
10
|
import { recordProductAnalyticsEvent } from "./product-analytics.js";
|
|
11
|
+
import { lockProjectGoalMutationAuthoritiesForOrganizationDeletion } from "./project-goal-mutation-fence.js";
|
|
9
12
|
export function organizationService(db) {
|
|
10
13
|
const DEFAULT_ISSUE_LABELS = [
|
|
11
14
|
{ name: "Bug", color: "#ef4444" },
|
|
@@ -202,6 +205,14 @@ export function organizationService(db) {
|
|
|
202
205
|
const created = await createCompanyWithUniqueKeys(tx, {
|
|
203
206
|
...organizationData,
|
|
204
207
|
});
|
|
208
|
+
await tx
|
|
209
|
+
.insert(organizationMutationState)
|
|
210
|
+
.values({ orgId: created.id })
|
|
211
|
+
.onConflictDoNothing({ target: organizationMutationState.orgId });
|
|
212
|
+
await tx
|
|
213
|
+
.insert(organizationBrandingMutationState)
|
|
214
|
+
.values({ orgId: created.id })
|
|
215
|
+
.onConflictDoNothing({ target: organizationBrandingMutationState.orgId });
|
|
205
216
|
await tx.insert(labels).values(DEFAULT_ISSUE_LABELS.map((label) => ({
|
|
206
217
|
orgId: created.id,
|
|
207
218
|
name: label.name,
|
|
@@ -238,6 +249,10 @@ export function organizationService(db) {
|
|
|
238
249
|
return enrichCompany(hydrated);
|
|
239
250
|
}),
|
|
240
251
|
update: (id, data) => db.transaction(async (tx) => {
|
|
252
|
+
const writesBrandColor = Object.prototype.hasOwnProperty.call(data, "brandColor");
|
|
253
|
+
if (writesBrandColor)
|
|
254
|
+
await lockNodeOrganizationBrandingAuthority(tx, id);
|
|
255
|
+
await lockNodeMutationAuthority(tx, id);
|
|
241
256
|
const existing = await getCompanyQuery(tx)
|
|
242
257
|
.where(eq(organizations.id, id))
|
|
243
258
|
.then((rows) => rows[0] ?? null);
|
|
@@ -334,6 +349,7 @@ export function organizationService(db) {
|
|
|
334
349
|
return enrichCompany(hydrated, aliases.map((alias) => alias.prefix));
|
|
335
350
|
}),
|
|
336
351
|
archive: (id) => db.transaction(async (tx) => {
|
|
352
|
+
await lockNodeMutationAuthority(tx, id);
|
|
337
353
|
const updated = await tx
|
|
338
354
|
.update(organizations)
|
|
339
355
|
.set({ status: "archived", updatedAt: new Date() })
|
|
@@ -351,6 +367,8 @@ export function organizationService(db) {
|
|
|
351
367
|
return enrichCompany(hydrated);
|
|
352
368
|
}),
|
|
353
369
|
remove: (id) => db.transaction(async (tx) => {
|
|
370
|
+
await lockOrganizationBrandingAuthorityForDeletion(tx, id);
|
|
371
|
+
await lockProjectGoalMutationAuthoritiesForOrganizationDeletion(tx, id);
|
|
354
372
|
// Delete from child tables in dependency order
|
|
355
373
|
await tx.delete(issueBlockAuditAttempts).where(eq(issueBlockAuditAttempts.orgId, id));
|
|
356
374
|
await tx.delete(requests).where(eq(requests.orgId, id));
|
|
@@ -396,8 +414,8 @@ export function organizationService(db) {
|
|
|
396
414
|
await tx.delete(organizationIssuePrefixAliases).where(eq(organizationIssuePrefixAliases.orgId, id));
|
|
397
415
|
await tx.delete(assets).where(eq(assets.orgId, id));
|
|
398
416
|
await tx.delete(projectGoals).where(eq(projectGoals.orgId, id));
|
|
399
|
-
await tx.delete(goals).where(eq(goals.orgId, id));
|
|
400
417
|
await tx.delete(projects).where(eq(projects.orgId, id));
|
|
418
|
+
await tx.delete(goals).where(eq(goals.orgId, id));
|
|
401
419
|
await tx.delete(agents).where(eq(agents.orgId, id));
|
|
402
420
|
const rows = await tx
|
|
403
421
|
.delete(organizations)
|