@tangle-network/agent-app 0.43.56 → 0.43.58
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/DesignCanvas-UNL34LS2.js +12 -0
- package/dist/DesignCanvasEditor-FJIVYOA2.js +13 -0
- package/dist/IntakeInterview-3ZIQMZS6.js +8 -0
- package/dist/InvitationsPanel-CJ7DR4F7.js +8 -0
- package/dist/MembersPanel-BB4OD2KB.js +8 -0
- package/dist/{TimelineEditor-I35SJJAJ.js → TimelineEditor-2ECKSU6P.js} +3 -3
- package/dist/{VaultPane-DrQ_tH6c.d.ts → VaultPane-BDP6SB_K.d.ts} +1 -0
- package/dist/{access-BZ81Btt5.d.ts → access-490O6aOU.d.ts} +16 -1
- package/dist/app-auth/index.d.ts +6 -1
- package/dist/app-auth/index.js +3 -3
- package/dist/app-auth/index.js.map +1 -1
- package/dist/{apply-Gk4raT2j.d.ts → apply-6wlMOLf8.d.ts} +30 -1
- package/dist/assets/index.d.ts +41 -0
- package/dist/assets/index.js +1 -1
- package/dist/assistant/index.d.ts +26 -6
- package/dist/assistant/index.js +9 -9
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-D0csjCvz.d.ts → attachment-validation-tPRp-PP1.d.ts} +7 -1
- package/dist/{auth-DuptSkWh.d.ts → auth-_FU8w01b.d.ts} +4 -1
- package/dist/billing/index.d.ts +8 -0
- package/dist/billing/index.js +1 -1
- package/dist/brand/index.d.ts +2 -0
- package/dist/brand/index.js.map +1 -1
- package/dist/brand-extraction/index.d.ts +3 -0
- package/dist/brand-extraction/index.js.map +1 -1
- package/dist/catalog/index.d.ts +2 -0
- package/dist/catalog/index.js +1 -1
- package/dist/chat-routes/index.d.ts +41 -10
- package/dist/chat-routes/index.js +15 -15
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +18 -4
- package/dist/chat-store/index.js +4 -4
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-H5FM75MR.js → chunk-22GLESKE.js} +3 -3
- package/dist/chunk-22GLESKE.js.map +1 -0
- package/dist/{chunk-WEBBJBDH.js → chunk-2DRYTJHI.js} +1 -1
- package/dist/chunk-2DRYTJHI.js.map +1 -0
- package/dist/{chunk-7EVZUIHW.js → chunk-3EJ6SFJI.js} +1 -1
- package/dist/chunk-3EJ6SFJI.js.map +1 -0
- package/dist/{chunk-HRJJH3RX.js → chunk-3OFQFW5K.js} +4 -4
- package/dist/chunk-3OFQFW5K.js.map +1 -0
- package/dist/{chunk-7SUPA2YT.js → chunk-4KKXZQPT.js} +2 -2
- package/dist/chunk-4KKXZQPT.js.map +1 -0
- package/dist/{chunk-5PTGEJZL.js → chunk-5SD6HT2V.js} +1 -1
- package/dist/chunk-5SD6HT2V.js.map +1 -0
- package/dist/{chunk-7C64WKAH.js → chunk-5SXS3YAB.js} +2 -2
- package/dist/{chunk-L2TG5DBW.js → chunk-5TESA4SN.js} +1 -1
- package/dist/chunk-5TESA4SN.js.map +1 -0
- package/dist/{chunk-63CE7FEZ.js → chunk-6XIAPIW6.js} +1 -1
- package/dist/chunk-6XIAPIW6.js.map +1 -0
- package/dist/{chunk-MFRCM32T.js → chunk-7CTIUCQ4.js} +1 -1
- package/dist/chunk-7CTIUCQ4.js.map +1 -0
- package/dist/{chunk-6KVH5SP5.js → chunk-7YW6EBDX.js} +2 -2
- package/dist/chunk-7YW6EBDX.js.map +1 -0
- package/dist/{chunk-ZXNXAQAH.js → chunk-AP2UXKUK.js} +1 -1
- package/dist/chunk-AP2UXKUK.js.map +1 -0
- package/dist/{chunk-K63OWUDJ.js → chunk-BPSV5AA2.js} +2 -2
- package/dist/chunk-BPSV5AA2.js.map +1 -0
- package/dist/{chunk-MFI4ZM7W.js → chunk-BT4QFCTB.js} +1 -1
- package/dist/chunk-BT4QFCTB.js.map +1 -0
- package/dist/{chunk-ZYBWGSAZ.js → chunk-BVKQKGRK.js} +1 -1
- package/dist/chunk-BVKQKGRK.js.map +1 -0
- package/dist/{chunk-RVUISYUE.js → chunk-C3NSB66C.js} +7 -7
- package/dist/chunk-C3NSB66C.js.map +1 -0
- package/dist/{chunk-BQV42AFK.js → chunk-CCVWR33L.js} +1 -1
- package/dist/chunk-CCVWR33L.js.map +1 -0
- package/dist/{chunk-MCJSS6SM.js → chunk-CQZSAR77.js} +1 -1
- package/dist/chunk-CQZSAR77.js.map +1 -0
- package/dist/{chunk-3SVAA3MA.js → chunk-DJ4VJIH5.js} +1 -1
- package/dist/chunk-DJ4VJIH5.js.map +1 -0
- package/dist/{chunk-XKBIYSSM.js → chunk-DTSPS2HV.js} +3 -3
- package/dist/chunk-DTSPS2HV.js.map +1 -0
- package/dist/{chunk-4QN75UHX.js → chunk-E7N67FYI.js} +8 -8
- package/dist/chunk-E7N67FYI.js.map +1 -0
- package/dist/{chunk-2QI7XV2T.js → chunk-FBVLEGEG.js} +1 -1
- package/dist/{chunk-2QI7XV2T.js.map → chunk-FBVLEGEG.js.map} +1 -1
- package/dist/{chunk-5RMIUJDI.js → chunk-FKCB5FTD.js} +1 -1
- package/dist/chunk-FKCB5FTD.js.map +1 -0
- package/dist/{chunk-EEPJGZJW.js → chunk-GPBRL2XR.js} +1 -1
- package/dist/chunk-GPBRL2XR.js.map +1 -0
- package/dist/{chunk-G3HCU7TA.js → chunk-GXE6KHTS.js} +1 -1
- package/dist/chunk-GXE6KHTS.js.map +1 -0
- package/dist/{chunk-65P3HJY3.js → chunk-HCOROIRT.js} +1 -1
- package/dist/chunk-HCOROIRT.js.map +1 -0
- package/dist/{chunk-54GSK2KK.js → chunk-HYTIHYNV.js} +2 -2
- package/dist/chunk-HYTIHYNV.js.map +1 -0
- package/dist/{chunk-5MG74GVQ.js → chunk-IG46XCZM.js} +3 -3
- package/dist/chunk-IG46XCZM.js.map +1 -0
- package/dist/{chunk-ND3XEGBE.js → chunk-JDEGS53O.js} +1 -1
- package/dist/chunk-JDEGS53O.js.map +1 -0
- package/dist/{chunk-7W5XSTUF.js → chunk-JML7WKWU.js} +1 -1
- package/dist/chunk-JML7WKWU.js.map +1 -0
- package/dist/{chunk-Q4TKVF3L.js → chunk-JTLYXUEV.js} +1 -1
- package/dist/chunk-JTLYXUEV.js.map +1 -0
- package/dist/{chunk-CCRVDLV3.js → chunk-JWBZ74TW.js} +2 -2
- package/dist/chunk-JWBZ74TW.js.map +1 -0
- package/dist/{chunk-ZU5GNSOJ.js → chunk-K3LIPKFV.js} +3 -3
- package/dist/chunk-K3LIPKFV.js.map +1 -0
- package/dist/{chunk-GQRL6VNU.js → chunk-LGBO24FL.js} +1 -1
- package/dist/chunk-LGBO24FL.js.map +1 -0
- package/dist/{chunk-SWUVTGMR.js → chunk-MEUNTJL5.js} +3 -3
- package/dist/{chunk-SWUVTGMR.js.map → chunk-MEUNTJL5.js.map} +1 -1
- package/dist/{chunk-YNALYIQ3.js → chunk-NLSSFLLS.js} +1 -1
- package/dist/chunk-NLSSFLLS.js.map +1 -0
- package/dist/{chunk-HFC4BTWJ.js → chunk-PTUMBMVH.js} +1 -1
- package/dist/chunk-PTUMBMVH.js.map +1 -0
- package/dist/{chunk-R4NOKZMD.js → chunk-ROLFDOMJ.js} +3 -3
- package/dist/{chunk-NYATNLRK.js → chunk-RT5RKAFX.js} +1 -1
- package/dist/chunk-RT5RKAFX.js.map +1 -0
- package/dist/{chunk-GNL3MG5J.js → chunk-S564OFTL.js} +2 -2
- package/dist/{chunk-GUSCYE3S.js → chunk-TQOFR6XY.js} +1 -1
- package/dist/chunk-TQOFR6XY.js.map +1 -0
- package/dist/{chunk-47C6PJCW.js → chunk-TZO3SOUL.js} +1 -1
- package/dist/chunk-TZO3SOUL.js.map +1 -0
- package/dist/{chunk-YGUNTIT5.js → chunk-UKTYKG57.js} +1 -1
- package/dist/chunk-UKTYKG57.js.map +1 -0
- package/dist/{chunk-23LUHHHN.js → chunk-VCOD5AOR.js} +1 -1
- package/dist/chunk-VCOD5AOR.js.map +1 -0
- package/dist/{chunk-5I72FRZY.js → chunk-VOJB7MVG.js} +2 -2
- package/dist/{chunk-XWJXZ6WE.js → chunk-VTWKH4YU.js} +2 -2
- package/dist/chunk-VTWKH4YU.js.map +1 -0
- package/dist/{chunk-4K7BZYO7.js → chunk-VWAEWOWK.js} +3 -3
- package/dist/chunk-VWAEWOWK.js.map +1 -0
- package/dist/{chunk-M2EBUDZ7.js → chunk-VWOPOLVO.js} +2 -2
- package/dist/chunk-VWOPOLVO.js.map +1 -0
- package/dist/{chunk-BN2USWIR.js → chunk-VYECQAGM.js} +2 -2
- package/dist/chunk-VYECQAGM.js.map +1 -0
- package/dist/{chunk-SIXYZ2FB.js → chunk-YJMCRXQQ.js} +1 -1
- package/dist/chunk-YJMCRXQQ.js.map +1 -0
- package/dist/{chunk-PJC4NXPA.js → chunk-YKBDH2UY.js} +3 -3
- package/dist/chunk-YKBDH2UY.js.map +1 -0
- package/dist/{chunk-XAWFPMAR.js → chunk-YKOM4ZLQ.js} +1 -1
- package/dist/chunk-YKOM4ZLQ.js.map +1 -0
- package/dist/{chunk-TKVJE63N.js → chunk-YRMLYFVR.js} +3 -3
- package/dist/chunk-YRMLYFVR.js.map +1 -0
- package/dist/{chunk-USYXHDKE.js → chunk-YZ6KQULN.js} +2 -2
- package/dist/{command-stack-B_-WNfZU.d.ts → command-stack-TNPWJCzW.d.ts} +14 -2
- package/dist/{completion-Kaqp_tWk.d.ts → completion-CvcNUz07.d.ts} +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/{contract-KfqJh_au.d.ts → contract-TjB68uqA.d.ts} +17 -0
- package/dist/design-canvas/drizzle.d.ts +10 -2
- package/dist/design-canvas/drizzle.js.map +1 -1
- package/dist/design-canvas/index.d.ts +29 -11
- package/dist/design-canvas/index.js +1070 -17
- package/dist/design-canvas/index.js.map +1 -1
- package/dist/design-canvas-react/engine.d.ts +42 -6
- package/dist/design-canvas-react/engine.js +4 -4
- package/dist/design-canvas-react/index.d.ts +12 -7
- package/dist/design-canvas-react/index.js +11 -11
- package/dist/design-canvas-react/index.js.map +1 -1
- package/dist/design-canvas-react/lazy.d.ts +4 -4
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/durable-chat/index.d.ts +46 -1
- package/dist/durable-chat/index.js +3 -3
- package/dist/eval/index.d.ts +1 -1
- package/dist/{export-presets-DAsU5Wal.d.ts → export-presets-mgVulRaV.d.ts} +10 -1
- package/dist/{flow-types-Cb_AblZs.d.ts → flow-types-CqomVAUN.d.ts} +1 -0
- package/dist/harness/index.d.ts +5 -0
- package/dist/harness/index.js +1 -1
- package/dist/index.d.ts +10 -18
- package/dist/index.js +30 -231
- package/dist/intakes/api.d.ts +3 -2
- package/dist/intakes/api.js +3 -3
- package/dist/intakes/api.js.map +1 -1
- package/dist/intakes/drizzle.d.ts +10 -2
- package/dist/intakes/drizzle.js +3 -3
- package/dist/intakes/drizzle.js.map +1 -1
- package/dist/intakes/index.d.ts +2 -2
- package/dist/intakes/index.js +2 -2
- package/dist/intakes-react/index.d.ts +2 -1
- package/dist/intakes-react/index.js +2 -2
- package/dist/intakes-react/lazy.d.ts +2 -1
- package/dist/intakes-react/lazy.js +1 -1
- package/dist/intakes-react/lazy.js.map +1 -1
- package/dist/integrations/index.d.ts +4 -0
- package/dist/integrations/index.js +1 -1
- package/dist/interactions/index.d.ts +13 -2
- package/dist/interactions/index.js +2 -2
- package/dist/{invitations-CZ5G2tIn.d.ts → invitations-DoYMvXea.d.ts} +11 -1
- package/dist/{invitations-schema-DaMO4hZE.d.ts → invitations-schema-_fI7HkJP.d.ts} +5 -1
- package/dist/knowledge/index.d.ts +2 -0
- package/dist/knowledge/index.js +1 -1
- package/dist/knowledge-loop/index.d.ts +4 -1
- package/dist/knowledge-loop/index.js +1 -1
- package/dist/{lazy-C7LiVeFt.d.ts → lazy-DrkVyDpe.d.ts} +2 -2
- package/dist/{mcp-BHfIoGLW.d.ts → mcp-BjPqQ-3C.d.ts} +4 -2
- package/dist/{mcp-rpc-DLw_r9PQ.d.ts → mcp-rpc-BfUOsalw.d.ts} +3 -0
- package/dist/missions/index.d.ts +25 -0
- package/dist/missions/index.js +1 -1
- package/dist/{model-Bzi5i63l.d.ts → model-C8U0HZxL.d.ts} +1 -0
- package/dist/{model-dF2h4xT9.d.ts → model-DmdkIteM.d.ts} +19 -0
- package/dist/{model-BBwyOuWG.d.ts → model-DnfO_PdJ.d.ts} +25 -1
- package/dist/model-resolution/index.d.ts +10 -0
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/{operations-DLIvvD00.d.ts → operations-ByJ6yXOW.d.ts} +20 -2
- package/dist/{parts-1sRYSPjF.d.ts → parts-n9XUon0a.d.ts} +29 -1
- package/dist/plans/index.d.ts +6 -0
- package/dist/plans/index.js +1 -1
- package/dist/platform/index.d.ts +24 -2
- package/dist/platform/index.js +3 -3
- package/dist/platform/index.js.map +1 -1
- package/dist/preflight/index.d.ts +3 -0
- package/dist/preflight/index.js +1 -1
- package/dist/preset-cloudflare/index.d.ts +4 -1
- package/dist/preset-cloudflare/index.js +2 -2
- package/dist/redact/index.d.ts +5 -0
- package/dist/redact/index.js +1 -1
- package/dist/{roles-BC1n4t37.d.ts → roles-B4nU3jDi.d.ts} +12 -0
- package/dist/run/index.d.ts +3 -0
- package/dist/run/index.js +1 -1
- package/dist/run/index.js.map +1 -1
- package/dist/runtime/index.d.ts +11 -4
- package/dist/runtime/index.js +4 -4
- package/dist/sandbox/index.d.ts +96 -3
- package/dist/sandbox/index.js +6 -6
- package/dist/{sandbox-terminal-BIIC__CP.d.ts → sandbox-terminal-ChNEdHF8.d.ts} +5 -0
- package/dist/sequences/drizzle.d.ts +11 -1
- package/dist/sequences/drizzle.js +1 -1
- package/dist/sequences/drizzle.js.map +1 -1
- package/dist/sequences/index.d.ts +32 -7
- package/dist/sequences/index.js +1562 -47
- package/dist/sequences/index.js.map +1 -1
- package/dist/sequences-react/index.d.ts +32 -2
- package/dist/sequences-react/index.js +3 -3
- package/dist/sequences-react/index.js.map +1 -1
- package/dist/{sso-CNOsARMJ.d.ts → sso-Cm00S61u.d.ts} +13 -0
- package/dist/{stale-turn-lock-C8Na1cFZ.d.ts → stale-turn-lock-DucQzvXu.d.ts} +2 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.js.map +1 -1
- package/dist/{store-gckrNq-g.d.ts → store-B7LLlk9p.d.ts} +22 -1
- package/dist/{store-CFyxgTlD.d.ts → store-CqfDtnPQ.d.ts} +8 -2
- package/dist/stream/index.d.ts +10 -4
- package/dist/stream/index.js +4 -4
- package/dist/{stream-normalizer-DWvtmY6F.d.ts → stream-normalizer-C3M--r6y.d.ts} +17 -1
- package/dist/studio/index.d.ts +37 -0
- package/dist/studio/index.js +1 -1
- package/dist/studio-react/index.d.ts +3 -0
- package/dist/studio-react/index.js +1 -1
- package/dist/studio-react/index.js.map +1 -1
- package/dist/tangle/index.d.ts +3 -0
- package/dist/tangle/index.js +1 -1
- package/dist/teams/drizzle.d.ts +8 -4
- package/dist/teams/drizzle.js +1 -1
- package/dist/teams/drizzle.js.map +1 -1
- package/dist/teams/index.d.ts +4 -2
- package/dist/teams/index.js +3 -3
- package/dist/teams/invitations-api.d.ts +10 -4
- package/dist/teams/invitations-api.js +4 -4
- package/dist/teams/invitations-api.js.map +1 -1
- package/dist/teams/members-api.d.ts +4 -2
- package/dist/teams/members-api.js +3 -3
- package/dist/teams/resend.d.ts +5 -4
- package/dist/teams/resend.js +1 -1
- package/dist/teams/resend.js.map +1 -1
- package/dist/teams-react/index.d.ts +7 -2
- package/dist/teams-react/index.js +3 -3
- package/dist/teams-react/lazy.d.ts +5 -2
- package/dist/teams-react/lazy.js +2 -2
- package/dist/teams-react/lazy.js.map +1 -1
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.js +1 -1
- package/dist/theme/tailwind-preset.d.ts +1 -0
- package/dist/theme/tailwind-preset.js.map +1 -1
- package/dist/theme-contract/cli.js +1 -1
- package/dist/theme-contract/index.d.ts +3 -0
- package/dist/theme-contract/index.js +1 -1
- package/dist/tools/index.d.ts +13 -6
- package/dist/tools/index.js +3 -3
- package/dist/trace/index.d.ts +6 -1
- package/dist/trace/index.js +2 -2
- package/dist/{turn-buffer-C9mEgoop.d.ts → turn-buffer-DGnAPKwa.d.ts} +6 -0
- package/dist/turn-stream/index.d.ts +28 -2
- package/dist/turn-stream/index.js +1 -1
- package/dist/turn-stream/index.js.map +1 -1
- package/dist/{types-BEOvc_ue.d.ts → types-CBRyqijY.d.ts} +10 -0
- package/dist/vault/index.d.ts +1 -1
- package/dist/vault/lazy.d.ts +3 -2
- package/dist/vault/lazy.js.map +1 -1
- package/dist/web/index.d.ts +4 -0
- package/dist/web/index.js +1 -1
- package/dist/web-react/index.d.ts +51 -8
- package/dist/web-react/index.js +9 -9
- package/dist/web-react/terminal.d.ts +2 -2
- package/dist/web-react/terminal.js +1 -1
- package/package.json +3 -2
- package/dist/DesignCanvas-5DJAFDNC.js +0 -12
- package/dist/DesignCanvasEditor-XUJZARSN.js +0 -13
- package/dist/IntakeInterview-WHVKMNQW.js +0 -8
- package/dist/InvitationsPanel-XUUWZBPV.js +0 -8
- package/dist/MembersPanel-FOVHWUQ3.js +0 -8
- package/dist/chunk-23LUHHHN.js.map +0 -1
- package/dist/chunk-3SVAA3MA.js.map +0 -1
- package/dist/chunk-47C6PJCW.js.map +0 -1
- package/dist/chunk-4K7BZYO7.js.map +0 -1
- package/dist/chunk-4QN75UHX.js.map +0 -1
- package/dist/chunk-54GSK2KK.js.map +0 -1
- package/dist/chunk-5MG74GVQ.js.map +0 -1
- package/dist/chunk-5PTGEJZL.js.map +0 -1
- package/dist/chunk-5RMIUJDI.js.map +0 -1
- package/dist/chunk-63CE7FEZ.js.map +0 -1
- package/dist/chunk-65P3HJY3.js.map +0 -1
- package/dist/chunk-6KVH5SP5.js.map +0 -1
- package/dist/chunk-7EVZUIHW.js.map +0 -1
- package/dist/chunk-7SUPA2YT.js.map +0 -1
- package/dist/chunk-7W5XSTUF.js.map +0 -1
- package/dist/chunk-BN2USWIR.js.map +0 -1
- package/dist/chunk-BQV42AFK.js.map +0 -1
- package/dist/chunk-CCRVDLV3.js.map +0 -1
- package/dist/chunk-EEPJGZJW.js.map +0 -1
- package/dist/chunk-G3HCU7TA.js.map +0 -1
- package/dist/chunk-GQRL6VNU.js.map +0 -1
- package/dist/chunk-GUSCYE3S.js.map +0 -1
- package/dist/chunk-H5FM75MR.js.map +0 -1
- package/dist/chunk-HFC4BTWJ.js.map +0 -1
- package/dist/chunk-HRJJH3RX.js.map +0 -1
- package/dist/chunk-K63OWUDJ.js.map +0 -1
- package/dist/chunk-L2TG5DBW.js.map +0 -1
- package/dist/chunk-M2EBUDZ7.js.map +0 -1
- package/dist/chunk-MCJSS6SM.js.map +0 -1
- package/dist/chunk-MFI4ZM7W.js.map +0 -1
- package/dist/chunk-MFRCM32T.js.map +0 -1
- package/dist/chunk-ND3XEGBE.js.map +0 -1
- package/dist/chunk-NEOV2NQ3.js +0 -1617
- package/dist/chunk-NEOV2NQ3.js.map +0 -1
- package/dist/chunk-NYATNLRK.js.map +0 -1
- package/dist/chunk-PJC4NXPA.js.map +0 -1
- package/dist/chunk-Q4TKVF3L.js.map +0 -1
- package/dist/chunk-RVUISYUE.js.map +0 -1
- package/dist/chunk-SIXYZ2FB.js.map +0 -1
- package/dist/chunk-TKVJE63N.js.map +0 -1
- package/dist/chunk-WEBBJBDH.js.map +0 -1
- package/dist/chunk-WFMUDX5J.js +0 -1092
- package/dist/chunk-WFMUDX5J.js.map +0 -1
- package/dist/chunk-XAWFPMAR.js.map +0 -1
- package/dist/chunk-XKBIYSSM.js.map +0 -1
- package/dist/chunk-XWJXZ6WE.js.map +0 -1
- package/dist/chunk-YGUNTIT5.js.map +0 -1
- package/dist/chunk-YNALYIQ3.js.map +0 -1
- package/dist/chunk-ZU5GNSOJ.js.map +0 -1
- package/dist/chunk-ZXNXAQAH.js.map +0 -1
- package/dist/chunk-ZYBWGSAZ.js.map +0 -1
- /package/dist/{DesignCanvas-5DJAFDNC.js.map → DesignCanvas-UNL34LS2.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-XUJZARSN.js.map → DesignCanvasEditor-FJIVYOA2.js.map} +0 -0
- /package/dist/{IntakeInterview-WHVKMNQW.js.map → IntakeInterview-3ZIQMZS6.js.map} +0 -0
- /package/dist/{InvitationsPanel-XUUWZBPV.js.map → InvitationsPanel-CJ7DR4F7.js.map} +0 -0
- /package/dist/{MembersPanel-FOVHWUQ3.js.map → MembersPanel-BB4OD2KB.js.map} +0 -0
- /package/dist/{TimelineEditor-I35SJJAJ.js.map → TimelineEditor-2ECKSU6P.js.map} +0 -0
- /package/dist/{chunk-7C64WKAH.js.map → chunk-5SXS3YAB.js.map} +0 -0
- /package/dist/{chunk-R4NOKZMD.js.map → chunk-ROLFDOMJ.js.map} +0 -0
- /package/dist/{chunk-GNL3MG5J.js.map → chunk-S564OFTL.js.map} +0 -0
- /package/dist/{chunk-5I72FRZY.js.map → chunk-VOJB7MVG.js.map} +0 -0
- /package/dist/{chunk-USYXHDKE.js.map → chunk-YZ6KQULN.js.map} +0 -0
package/dist/tangle/index.js
CHANGED
package/dist/teams/drizzle.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { T as TeamDatabase, a as TeamTables, O as OrganizationRow, b as OrganizationMemberRow } from '../access-
|
|
2
|
-
export { C as CreateAccessOptions, c as CreateOrganizationAccessOptions, d as CreateTeamTablesOptions, e as OrganizationAccess, f as OrganizationAccessApi, g as TeamParentTable, U as UserWorkspaceSummary, W as WorkspaceAccess, h as WorkspaceAccessApi, i as WorkspaceAccessTable, j as WorkspaceMemberRow, k as createOrganizationAccess, l as createTeamTables, m as createWorkspaceAccess } from '../access-
|
|
3
|
-
export { C as CreateWorkspaceInvitationTableOptions, W as WorkspaceInvitationRow, a as WorkspaceInvitationTables, c as createWorkspaceInvitationTable } from '../invitations-schema-
|
|
4
|
-
import { O as OrganizationRole } from '../roles-
|
|
1
|
+
import { T as TeamDatabase, a as TeamTables, O as OrganizationRow, b as OrganizationMemberRow } from '../access-490O6aOU.js';
|
|
2
|
+
export { C as CreateAccessOptions, c as CreateOrganizationAccessOptions, d as CreateTeamTablesOptions, e as OrganizationAccess, f as OrganizationAccessApi, g as TeamParentTable, U as UserWorkspaceSummary, W as WorkspaceAccess, h as WorkspaceAccessApi, i as WorkspaceAccessTable, j as WorkspaceMemberRow, k as createOrganizationAccess, l as createTeamTables, m as createWorkspaceAccess } from '../access-490O6aOU.js';
|
|
3
|
+
export { C as CreateWorkspaceInvitationTableOptions, W as WorkspaceInvitationRow, a as WorkspaceInvitationTables, c as createWorkspaceInvitationTable } from '../invitations-schema-_fI7HkJP.js';
|
|
4
|
+
import { O as OrganizationRole } from '../roles-B4nU3jDi.js';
|
|
5
5
|
import 'drizzle-orm/sqlite-core';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -24,20 +24,24 @@ import 'drizzle-orm/sqlite-core';
|
|
|
24
24
|
* keeps reads side-effect-free; provisioning is this explicit call.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
/** Define the structure for a user within a personal organization context */
|
|
27
28
|
interface EnsurePersonalOrganizationUser {
|
|
28
29
|
id: string;
|
|
29
30
|
name?: string | null;
|
|
30
31
|
email?: string | null;
|
|
31
32
|
}
|
|
33
|
+
/** Describe a personal organization result including organization, member, and role details */
|
|
32
34
|
interface PersonalOrganizationResult {
|
|
33
35
|
organization: OrganizationRow;
|
|
34
36
|
member: OrganizationMemberRow;
|
|
35
37
|
role: OrganizationRole;
|
|
36
38
|
}
|
|
39
|
+
/** Define options required to create a personal organization including database and tables references */
|
|
37
40
|
interface CreatePersonalOrganizationOptions {
|
|
38
41
|
db: TeamDatabase;
|
|
39
42
|
tables: TeamTables;
|
|
40
43
|
}
|
|
44
|
+
/** Ensure a user has a personal organization by creating or retrieving it as needed */
|
|
41
45
|
declare function createEnsurePersonalOrganization(opts: CreatePersonalOrganizationOptions): (user: EnsurePersonalOrganizationUser) => Promise<PersonalOrganizationResult>;
|
|
42
46
|
|
|
43
47
|
export { type CreatePersonalOrganizationOptions, type EnsurePersonalOrganizationUser, OrganizationMemberRow, OrganizationRow, type PersonalOrganizationResult, TeamDatabase, TeamTables, createEnsurePersonalOrganization };
|
package/dist/teams/drizzle.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/teams/drizzle/schema.ts","../../src/teams/drizzle/invitations-schema.ts","../../src/teams/drizzle/access.ts","../../src/teams/drizzle/personal-organization.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the teams tables. The product owns the user and\n * workspace tables; this factory creates the tenancy/membership tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics. Column names, types,\n * defaults, enums, and indexes mirror gtm's hand-rolled tables so a product\n * with those tables can adopt the factory without rewriting rows.\n *\n * The three tables and their roles:\n * - `organization` — the TENANT/ownership primitive. `kind` is 'personal'\n * (one auto-created per user; see ensurePersonalOrganization) or 'team'.\n * This is what owns workspaces and what billing/seats attach to.\n * - `organizationMember` — who belongs to an org and at what org role.\n * - `workspaceMember` — the additive invite/member surface: per-workspace\n * access grants, including pending email-only invites carrying a token.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/teams/drizzle` sub-subpath. The pure `./teams` leaf imports none of this,\n * so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type TeamParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\nexport interface CreateTeamTablesOptions {\n /** The product's user table — org/member rows reference `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — workspace members reference `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nexport function createTeamTables(opts: CreateTeamTablesOptions) {\n const { userTable, workspaceTable } = opts\n\n const organizations = sqliteTable('organization', {\n id: hexId(),\n name: text('name').notNull(),\n slug: text('slug').notNull().unique(),\n kind: text('kind', { enum: ['personal', 'team'] }).notNull().default('personal'),\n createdBy: text('created_by').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n index('idx_organization_created_by').on(table.createdBy),\n ])\n\n const organizationMembers = sqliteTable('organization_member', {\n id: hexId(),\n organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'member', 'billing'] }).notNull().default('member'),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_org_member_user').on(table.organizationId, table.userId),\n index('idx_org_member_user').on(table.userId),\n ])\n\n const workspaceMembers = sqliteTable('workspace_member', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationMemberId: text('organization_member_id').references(() => organizationMembers.id, { onDelete: 'cascade' }),\n userId: text('user_id').references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'editor', 'viewer'] }).notNull().default('editor'),\n invitedBy: text('invited_by'),\n inviteEmail: text('invite_email'),\n inviteToken: text('invite_token'),\n invitedAt: integer('invited_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_member_org_member').on(table.workspaceId, table.organizationMemberId),\n index('idx_workspace_member_user').on(table.workspaceId, table.userId),\n index('idx_member_user').on(table.userId),\n uniqueIndex('idx_member_invite_token').on(table.inviteToken),\n ])\n\n return { organizations, organizationMembers, workspaceMembers }\n}\n\nexport type TeamTables = ReturnType<typeof createTeamTables>\n\nexport type OrganizationRow = TeamTables['organizations']['$inferSelect']\nexport type OrganizationMemberRow = TeamTables['organizationMembers']['$inferSelect']\nexport type WorkspaceMemberRow = TeamTables['workspaceMembers']['$inferSelect']\n","/**\n * Drizzle schema factory for the dedicated workspace-invitation table. Separate\n * from `createTeamTables` on purpose: an app opts into the rich email-invitation\n * lifecycle (status / expiry / resend / revoke / preview) by calling this, and an\n * app that doesn't want it never grows the table — `createTeamTables`'s output and\n * migrations stay untouched.\n *\n * The product owns the user and workspace tables; `organizations` comes from\n * `createTeamTables`. Pass all three so the invitation's FKs wire into the same\n * drizzle schema with real cascade semantics. Call this AFTER `createTeamTables`\n * (so `organizations` exists) — the lazy `.references(() => ...)` closures make\n * the ordering within one module safe.\n *\n * Columns / enums / indexes mirror creative-agent's validated hand-rolled table,\n * so an app already running that table adopts this factory with no row rewrite.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { TeamParentTable } from './schema'\n\nexport interface CreateWorkspaceInvitationTableOptions {\n /** The product's user table — `invitedByUserId` references `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — `workspaceId` references `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n /** The `organizations` table from `createTeamTables` — `organizationId` references it. */\n organizationTable: TeamParentTable\n}\n\nexport function createWorkspaceInvitationTable(opts: CreateWorkspaceInvitationTableOptions) {\n const { userTable, workspaceTable, organizationTable } = opts\n\n const workspaceInvitations = sqliteTable('workspace_invitation', {\n id: text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationId: text('organization_id').notNull().references(() => organizationTable.id, { onDelete: 'cascade' }),\n email: text('email').notNull(),\n invitedByUserId: text('invited_by_user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n permissions: text('permissions', { enum: ['admin', 'editor', 'viewer'] }).notNull().default('editor'),\n token: text('token').notNull().unique(),\n status: text('status', { enum: ['pending', 'accepted', 'expired', 'revoked'] }).notNull().default('pending'),\n emailStatus: text('email_status', { enum: ['not_sent', 'sent', 'failed'] }).notNull().default('not_sent'),\n expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n revokedAt: integer('revoked_at', { mode: 'timestamp' }),\n lastSentAt: integer('last_sent_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_invitation_token').on(table.token),\n index('idx_workspace_invitation_workspace').on(table.workspaceId, table.createdAt),\n index('idx_workspace_invitation_email').on(table.email),\n index('idx_workspace_invitation_workspace_email_status').on(table.workspaceId, table.email, table.status),\n ])\n\n return { workspaceInvitations }\n}\n\nexport type WorkspaceInvitationTables = ReturnType<typeof createWorkspaceInvitationTable>\nexport type WorkspaceInvitationRow = WorkspaceInvitationTables['workspaceInvitations']['$inferSelect']\n","/**\n * RBAC access builders over the teams tables. `createWorkspaceAccess` and\n * `createOrganizationAccess` close over the product's `db`, the tables from\n * `createTeamTables`, and the product's `workspace` table, returning the exact\n * `getWorkspaceAccess` / `requireWorkspaceAccess` / `listUserWorkspaces` /\n * `getOrganizationAccess` / `requireOrganizationAccess` functions a consumer\n * already calls — so adoption is a one-line import swap, every call site stays\n * identical.\n *\n * Defense in depth: org owners/admins are workspace owners across the org;\n * everyone else gets their explicit per-workspace role. Effective role is the\n * fold in `resolveWorkspaceRole` (pure, from `../roles`). Every query pins\n * `userId`, so a leaked id can never read across the tenancy boundary — it\n * surfaces as \"not found\".\n *\n * Driver-agnostic: builders are awaited, never `.run()`/`.all()`, so\n * better-sqlite3, D1, and libsql handles all behave identically.\n */\n\nimport { and, desc, eq, isNotNull } from 'drizzle-orm'\nimport type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'\nimport {\n type OrganizationRole,\n type WorkspaceRole,\n hasOrganizationRole,\n hasWorkspaceRole,\n resolveWorkspaceRole,\n} from '../roles'\nimport type { TeamParentTable } from './schema'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\n/** Any SQLite drizzle database — `any` erases driver-specific generics so\n * better-sqlite3, D1, and libsql handles all fit. */\nexport type TeamDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>\n\n/**\n * The product's workspace table, narrowed to the columns the access joins read.\n * Adopters pass their real drizzle workspace table — it carries these columns\n * (gtm's does); the type only asserts the minimum the joins touch.\n */\nexport interface WorkspaceAccessTable {\n id: any\n organizationId: any\n name: any\n updatedAt: any\n}\n\nexport interface CreateAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The product's workspace table (the FK target passed to createTeamTables). */\n workspaceTable: TeamParentTable & WorkspaceAccessTable\n}\n\nexport interface WorkspaceAccess {\n workspace: Record<string, unknown>\n organization: OrganizationRow\n organizationMember: OrganizationMemberRow\n role: WorkspaceRole\n}\n\nexport interface UserWorkspaceSummary extends Record<string, unknown> {\n organizationId: string\n organizationName: string\n role: WorkspaceRole\n}\n\nexport interface WorkspaceAccessApi {\n getWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess | null>\n requireWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess>\n listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]>\n}\n\nexport function createWorkspaceAccess(opts: CreateAccessOptions): WorkspaceAccessApi {\n const { db, tables, workspaceTable } = opts\n const { organizations, organizationMembers, workspaceMembers } = tables\n const workspaces = workspaceTable\n\n async function getWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess | null> {\n const [row] = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n acceptedAt: workspaceMembers.acceptedAt,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n .where(eq(workspaces.id, workspaceId))\n .limit(1)\n\n if (!row) return null\n\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return null\n if (!hasWorkspaceRole(role, minRole)) return null\n\n return {\n workspace: row.workspace as Record<string, unknown>,\n organization: row.organization,\n organizationMember: row.organizationMember,\n role,\n }\n }\n\n async function requireWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess> {\n const access = await getWorkspaceAccess(workspaceId, userId, minRole)\n if (!access) throw new Response('Workspace not found', { status: 404 })\n return access\n }\n\n async function listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]> {\n const rows = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n // Most recently active workspace first, so callers taking rows[0] as the\n // default pick the freshest one.\n .orderBy(desc(workspaces.updatedAt))\n\n return rows.flatMap((row: any) => {\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return []\n return [{\n ...(row.workspace as Record<string, unknown>),\n organizationId: row.organization.id,\n organizationName: row.organization.name,\n role,\n } as UserWorkspaceSummary]\n })\n }\n\n return { getWorkspaceAccess, requireWorkspaceAccess, listUserWorkspaces }\n}\n\nexport interface OrganizationAccess {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\nexport interface OrganizationAccessApi {\n getOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess | null>\n requireOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess>\n}\n\nexport interface CreateOrganizationAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\nexport function createOrganizationAccess(opts: CreateOrganizationAccessOptions): OrganizationAccessApi {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n async function getOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess | null> {\n const [row] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.organizationId, organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .limit(1)\n\n if (!row) return null\n const role = row.member.role as OrganizationRole\n if (!hasOrganizationRole(role, minRole)) return null\n return { organization: row.organization, member: row.member, role }\n }\n\n async function requireOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess> {\n const access = await getOrganizationAccess(organizationId, userId, minRole)\n if (!access) throw new Response('Organization not found', { status: 404 })\n return access\n }\n\n return { getOrganizationAccess, requireOrganizationAccess }\n}\n","/**\n * `ensurePersonalOrganization` — the substrate that makes solo-user adoption\n * work. Every user gets exactly one auto-created `kind: 'personal'` org with\n * the user as `owner` member. That org is the tenant that owns their\n * workspaces, so role resolution (`getWorkspaceAccess`) has an org row to read\n * even before any team/invite exists.\n *\n * Idempotent by construction: the org `slug` is derived from the user id\n * (`personal-<id>`) and upserted; the membership upserts on the\n * (organizationId, userId) unique index. Concurrent calls converge — no\n * duplicate personal orgs, no duplicate owner rows.\n *\n * ADOPTION CONTRACT: call this once per user at first authenticated entry\n * (e.g. right after sign-in / session bootstrap) BEFORE the first\n * `getWorkspaceAccess`. Role resolution requires the org membership to exist;\n * an adopter that skips this will see `null` access for a brand-new user with\n * no org row. There is no implicit creation inside the access builders — that\n * keeps reads side-effect-free; provisioning is this explicit call.\n */\n\nimport { and, eq } from 'drizzle-orm'\nimport type { OrganizationRole } from '../roles'\nimport type { TeamDatabase } from './access'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\nexport interface EnsurePersonalOrganizationUser {\n id: string\n name?: string | null\n email?: string | null\n}\n\nexport interface PersonalOrganizationResult {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\nexport interface CreatePersonalOrganizationOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\nexport function createEnsurePersonalOrganization(opts: CreatePersonalOrganizationOptions) {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n return async function ensurePersonalOrganization(\n user: EnsurePersonalOrganizationUser,\n ): Promise<PersonalOrganizationResult> {\n const [existing] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.userId, user.id),\n eq(organizations.kind, 'personal'),\n ))\n .limit(1)\n\n if (existing) {\n return {\n organization: existing.organization,\n member: existing.member,\n role: existing.member.role as OrganizationRole,\n }\n }\n\n const orgName = user.name?.trim() || user.email?.split('@')[0] || 'Personal'\n const slug = `personal-${user.id}`\n const [organization] = await db\n .insert(organizations)\n .values({\n name: `${orgName}'s Organization`,\n slug,\n kind: 'personal',\n createdBy: user.id,\n })\n .onConflictDoUpdate({\n target: organizations.slug,\n set: { updatedAt: new Date() },\n })\n .returning()\n\n // A single-row upsert with .returning() always yields exactly one row.\n const org = organization as OrganizationRow\n\n const [member] = await db\n .insert(organizationMembers)\n .values({\n organizationId: org.id,\n userId: user.id,\n role: 'owner',\n })\n .onConflictDoUpdate({\n target: [organizationMembers.organizationId, organizationMembers.userId],\n set: { role: 'owner', updatedAt: new Date() },\n })\n .returning()\n\n return { organization: org, member: member as OrganizationMemberRow, role: 'owner' }\n }\n}\n"],"mappings":"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","names":["sql","index","integer","sqliteTable","text","uniqueIndex","and","eq"]}
|
|
1
|
+
{"version":3,"sources":["../../src/teams/drizzle/schema.ts","../../src/teams/drizzle/invitations-schema.ts","../../src/teams/drizzle/access.ts","../../src/teams/drizzle/personal-organization.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the teams tables. The product owns the user and\n * workspace tables; this factory creates the tenancy/membership tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics. Column names, types,\n * defaults, enums, and indexes mirror gtm's hand-rolled tables so a product\n * with those tables can adopt the factory without rewriting rows.\n *\n * The three tables and their roles:\n * - `organization` — the TENANT/ownership primitive. `kind` is 'personal'\n * (one auto-created per user; see ensurePersonalOrganization) or 'team'.\n * This is what owns workspaces and what billing/seats attach to.\n * - `organizationMember` — who belongs to an org and at what org role.\n * - `workspaceMember` — the additive invite/member surface: per-workspace\n * access grants, including pending email-only invites carrying a token.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/teams/drizzle` sub-subpath. The pure `./teams` leaf imports none of this,\n * so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type TeamParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\n/** Define options specifying user and workspace tables for creating team-related tables */\nexport interface CreateTeamTablesOptions {\n /** The product's user table — org/member rows reference `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — workspace members reference `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\n/** Build SQLite tables for organizations and related team structures using provided options */\nexport function createTeamTables(opts: CreateTeamTablesOptions) {\n const { userTable, workspaceTable } = opts\n\n const organizations = sqliteTable('organization', {\n id: hexId(),\n name: text('name').notNull(),\n slug: text('slug').notNull().unique(),\n kind: text('kind', { enum: ['personal', 'team'] }).notNull().default('personal'),\n createdBy: text('created_by').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n index('idx_organization_created_by').on(table.createdBy),\n ])\n\n const organizationMembers = sqliteTable('organization_member', {\n id: hexId(),\n organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'member', 'billing'] }).notNull().default('member'),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_org_member_user').on(table.organizationId, table.userId),\n index('idx_org_member_user').on(table.userId),\n ])\n\n const workspaceMembers = sqliteTable('workspace_member', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationMemberId: text('organization_member_id').references(() => organizationMembers.id, { onDelete: 'cascade' }),\n userId: text('user_id').references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'editor', 'viewer'] }).notNull().default('editor'),\n invitedBy: text('invited_by'),\n inviteEmail: text('invite_email'),\n inviteToken: text('invite_token'),\n invitedAt: integer('invited_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_member_org_member').on(table.workspaceId, table.organizationMemberId),\n index('idx_workspace_member_user').on(table.workspaceId, table.userId),\n index('idx_member_user').on(table.userId),\n uniqueIndex('idx_member_invite_token').on(table.inviteToken),\n ])\n\n return { organizations, organizationMembers, workspaceMembers }\n}\n\n/** Resolve team tables by deriving the return type of createTeamTables */\nexport type TeamTables = ReturnType<typeof createTeamTables>\n\n/** Resolve the structure of an organization row from the organizations table in TeamTables */\nexport type OrganizationRow = TeamTables['organizations']['$inferSelect']\n/** Resolve the structure of an organization member row from the team tables selection */\nexport type OrganizationMemberRow = TeamTables['organizationMembers']['$inferSelect']\n/** Resolve a workspace member row with selected fields from the workspaceMembers table */\nexport type WorkspaceMemberRow = TeamTables['workspaceMembers']['$inferSelect']\n","/**\n * Drizzle schema factory for the dedicated workspace-invitation table. Separate\n * from `createTeamTables` on purpose: an app opts into the rich email-invitation\n * lifecycle (status / expiry / resend / revoke / preview) by calling this, and an\n * app that doesn't want it never grows the table — `createTeamTables`'s output and\n * migrations stay untouched.\n *\n * The product owns the user and workspace tables; `organizations` comes from\n * `createTeamTables`. Pass all three so the invitation's FKs wire into the same\n * drizzle schema with real cascade semantics. Call this AFTER `createTeamTables`\n * (so `organizations` exists) — the lazy `.references(() => ...)` closures make\n * the ordering within one module safe.\n *\n * Columns / enums / indexes mirror creative-agent's validated hand-rolled table,\n * so an app already running that table adopts this factory with no row rewrite.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { TeamParentTable } from './schema'\n\n/** Define options for creating a workspace invitation table with user, workspace, and organization references */\nexport interface CreateWorkspaceInvitationTableOptions {\n /** The product's user table — `invitedByUserId` references `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — `workspaceId` references `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n /** The `organizations` table from `createTeamTables` — `organizationId` references it. */\n organizationTable: TeamParentTable\n}\n\n/** Build a workspace invitation table with defined columns and foreign key constraints */\nexport function createWorkspaceInvitationTable(opts: CreateWorkspaceInvitationTableOptions) {\n const { userTable, workspaceTable, organizationTable } = opts\n\n const workspaceInvitations = sqliteTable('workspace_invitation', {\n id: text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationId: text('organization_id').notNull().references(() => organizationTable.id, { onDelete: 'cascade' }),\n email: text('email').notNull(),\n invitedByUserId: text('invited_by_user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n permissions: text('permissions', { enum: ['admin', 'editor', 'viewer'] }).notNull().default('editor'),\n token: text('token').notNull().unique(),\n status: text('status', { enum: ['pending', 'accepted', 'expired', 'revoked'] }).notNull().default('pending'),\n emailStatus: text('email_status', { enum: ['not_sent', 'sent', 'failed'] }).notNull().default('not_sent'),\n expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n revokedAt: integer('revoked_at', { mode: 'timestamp' }),\n lastSentAt: integer('last_sent_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_invitation_token').on(table.token),\n index('idx_workspace_invitation_workspace').on(table.workspaceId, table.createdAt),\n index('idx_workspace_invitation_email').on(table.email),\n index('idx_workspace_invitation_workspace_email_status').on(table.workspaceId, table.email, table.status),\n ])\n\n return { workspaceInvitations }\n}\n\n/** Resolve the structure of workspace invitation tables from the creation function */\nexport type WorkspaceInvitationTables = ReturnType<typeof createWorkspaceInvitationTable>\n/** Resolve the structure of a workspace invitation row from the workspaceInvitations table */\nexport type WorkspaceInvitationRow = WorkspaceInvitationTables['workspaceInvitations']['$inferSelect']\n","/**\n * RBAC access builders over the teams tables. `createWorkspaceAccess` and\n * `createOrganizationAccess` close over the product's `db`, the tables from\n * `createTeamTables`, and the product's `workspace` table, returning the exact\n * `getWorkspaceAccess` / `requireWorkspaceAccess` / `listUserWorkspaces` /\n * `getOrganizationAccess` / `requireOrganizationAccess` functions a consumer\n * already calls — so adoption is a one-line import swap, every call site stays\n * identical.\n *\n * Defense in depth: org owners/admins are workspace owners across the org;\n * everyone else gets their explicit per-workspace role. Effective role is the\n * fold in `resolveWorkspaceRole` (pure, from `../roles`). Every query pins\n * `userId`, so a leaked id can never read across the tenancy boundary — it\n * surfaces as \"not found\".\n *\n * Driver-agnostic: builders are awaited, never `.run()`/`.all()`, so\n * better-sqlite3, D1, and libsql handles all behave identically.\n */\n\nimport { and, desc, eq, isNotNull } from 'drizzle-orm'\nimport type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'\nimport {\n type OrganizationRole,\n type WorkspaceRole,\n hasOrganizationRole,\n hasWorkspaceRole,\n resolveWorkspaceRole,\n} from '../roles'\nimport type { TeamParentTable } from './schema'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\n/** Any SQLite drizzle database — `any` erases driver-specific generics so\n * better-sqlite3, D1, and libsql handles all fit. */\nexport type TeamDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>\n\n/**\n * The product's workspace table, narrowed to the columns the access joins read.\n * Adopters pass their real drizzle workspace table — it carries these columns\n * (gtm's does); the type only asserts the minimum the joins touch.\n */\nexport interface WorkspaceAccessTable {\n id: any\n organizationId: any\n name: any\n updatedAt: any\n}\n\n/** Define options required to create access with database, tables, and workspace table references */\nexport interface CreateAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The product's workspace table (the FK target passed to createTeamTables). */\n workspaceTable: TeamParentTable & WorkspaceAccessTable\n}\n\n/** Define access details including workspace data, organization info, member info, and role within workspace */\nexport interface WorkspaceAccess {\n workspace: Record<string, unknown>\n organization: OrganizationRow\n organizationMember: OrganizationMemberRow\n role: WorkspaceRole\n}\n\n/** Describe a user's workspace details including organization and role information */\nexport interface UserWorkspaceSummary extends Record<string, unknown> {\n organizationId: string\n organizationName: string\n role: WorkspaceRole\n}\n\n/** Define methods to retrieve and enforce user access permissions within workspaces */\nexport interface WorkspaceAccessApi {\n getWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess | null>\n requireWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess>\n listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]>\n}\n\n/** Create workspace access API to manage user roles and permissions within a workspace */\nexport function createWorkspaceAccess(opts: CreateAccessOptions): WorkspaceAccessApi {\n const { db, tables, workspaceTable } = opts\n const { organizations, organizationMembers, workspaceMembers } = tables\n const workspaces = workspaceTable\n\n async function getWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess | null> {\n const [row] = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n acceptedAt: workspaceMembers.acceptedAt,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n .where(eq(workspaces.id, workspaceId))\n .limit(1)\n\n if (!row) return null\n\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return null\n if (!hasWorkspaceRole(role, minRole)) return null\n\n return {\n workspace: row.workspace as Record<string, unknown>,\n organization: row.organization,\n organizationMember: row.organizationMember,\n role,\n }\n }\n\n async function requireWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess> {\n const access = await getWorkspaceAccess(workspaceId, userId, minRole)\n if (!access) throw new Response('Workspace not found', { status: 404 })\n return access\n }\n\n async function listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]> {\n const rows = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n // Most recently active workspace first, so callers taking rows[0] as the\n // default pick the freshest one.\n .orderBy(desc(workspaces.updatedAt))\n\n return rows.flatMap((row: any) => {\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return []\n return [{\n ...(row.workspace as Record<string, unknown>),\n organizationId: row.organization.id,\n organizationName: row.organization.name,\n role,\n } as UserWorkspaceSummary]\n })\n }\n\n return { getWorkspaceAccess, requireWorkspaceAccess, listUserWorkspaces }\n}\n\n/** Define access details linking an organization, its member, and the member's role */\nexport interface OrganizationAccess {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\n/** Define methods to retrieve and enforce user access levels within an organization */\nexport interface OrganizationAccessApi {\n getOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess | null>\n requireOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess>\n}\n\n/** Define options required to create access for an organization including database and tables */\nexport interface CreateOrganizationAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\n/** Resolve organization access API with specified database and table options */\nexport function createOrganizationAccess(opts: CreateOrganizationAccessOptions): OrganizationAccessApi {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n async function getOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess | null> {\n const [row] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.organizationId, organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .limit(1)\n\n if (!row) return null\n const role = row.member.role as OrganizationRole\n if (!hasOrganizationRole(role, minRole)) return null\n return { organization: row.organization, member: row.member, role }\n }\n\n async function requireOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess> {\n const access = await getOrganizationAccess(organizationId, userId, minRole)\n if (!access) throw new Response('Organization not found', { status: 404 })\n return access\n }\n\n return { getOrganizationAccess, requireOrganizationAccess }\n}\n","/**\n * `ensurePersonalOrganization` — the substrate that makes solo-user adoption\n * work. Every user gets exactly one auto-created `kind: 'personal'` org with\n * the user as `owner` member. That org is the tenant that owns their\n * workspaces, so role resolution (`getWorkspaceAccess`) has an org row to read\n * even before any team/invite exists.\n *\n * Idempotent by construction: the org `slug` is derived from the user id\n * (`personal-<id>`) and upserted; the membership upserts on the\n * (organizationId, userId) unique index. Concurrent calls converge — no\n * duplicate personal orgs, no duplicate owner rows.\n *\n * ADOPTION CONTRACT: call this once per user at first authenticated entry\n * (e.g. right after sign-in / session bootstrap) BEFORE the first\n * `getWorkspaceAccess`. Role resolution requires the org membership to exist;\n * an adopter that skips this will see `null` access for a brand-new user with\n * no org row. There is no implicit creation inside the access builders — that\n * keeps reads side-effect-free; provisioning is this explicit call.\n */\n\nimport { and, eq } from 'drizzle-orm'\nimport type { OrganizationRole } from '../roles'\nimport type { TeamDatabase } from './access'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\n/** Define the structure for a user within a personal organization context */\nexport interface EnsurePersonalOrganizationUser {\n id: string\n name?: string | null\n email?: string | null\n}\n\n/** Describe a personal organization result including organization, member, and role details */\nexport interface PersonalOrganizationResult {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\n/** Define options required to create a personal organization including database and tables references */\nexport interface CreatePersonalOrganizationOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\n/** Ensure a user has a personal organization by creating or retrieving it as needed */\nexport function createEnsurePersonalOrganization(opts: CreatePersonalOrganizationOptions) {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n return async function ensurePersonalOrganization(\n user: EnsurePersonalOrganizationUser,\n ): Promise<PersonalOrganizationResult> {\n const [existing] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.userId, user.id),\n eq(organizations.kind, 'personal'),\n ))\n .limit(1)\n\n if (existing) {\n return {\n organization: existing.organization,\n member: existing.member,\n role: existing.member.role as OrganizationRole,\n }\n }\n\n const orgName = user.name?.trim() || user.email?.split('@')[0] || 'Personal'\n const slug = `personal-${user.id}`\n const [organization] = await db\n .insert(organizations)\n .values({\n name: `${orgName}'s Organization`,\n slug,\n kind: 'personal',\n createdBy: user.id,\n })\n .onConflictDoUpdate({\n target: organizations.slug,\n set: { updatedAt: new Date() },\n })\n .returning()\n\n // A single-row upsert with .returning() always yields exactly one row.\n const org = organization as OrganizationRow\n\n const [member] = await db\n .insert(organizationMembers)\n .values({\n organizationId: org.id,\n userId: user.id,\n role: 'owner',\n })\n .onConflictDoUpdate({\n target: [organizationMembers.organizationId, organizationMembers.userId],\n set: { role: 'owner', updatedAt: new Date() },\n })\n .returning()\n\n return { organization: org, member: member as OrganizationMemberRow, role: 'owner' }\n }\n}\n"],"mappings":"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","names":["sql","index","integer","sqliteTable","text","uniqueIndex","and","eq"]}
|
package/dist/teams/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { a as ASSIGNABLE_WORKSPACE_ROLES, A as AssignableWorkspaceRole, b as ORGANIZATION_ROLES, c as ORGANIZATION_ROLE_RANK, O as OrganizationRole, S as SandboxWorkspaceRole, W as WORKSPACE_ROLES, d as WORKSPACE_ROLE_RANK, e as WorkspaceCollaborationAccess, f as WorkspaceRole, g as canManageWorkspaceMemberRole, h as hasOrganizationRole, i as hasWorkspaceRole, j as isAssignableWorkspaceRole, o as organizationRoleGrantsWorkspaceOwner, r as resolveWorkspaceRole, w as workspaceRoleToCollaborationAccess, k as workspaceRoleToSandboxRole } from '../roles-
|
|
2
|
-
export { I as INVITATION_EXPIRY_DAYS, a as InvitationEmailBrand, b as InvitationEmailStatus, c as InvitationPermission, d as InvitationStatus, R as RenderInvitationEmailInput, e as RenderedInvitationEmail, g as generateInvitationToken, f as getInvitationExpiresAt, i as inviteUrlForToken, n as normalizeInvitationEmail, p as parseInvitationPermission, r as renderInvitationEmail } from '../invitations-
|
|
1
|
+
export { a as ASSIGNABLE_WORKSPACE_ROLES, A as AssignableWorkspaceRole, b as ORGANIZATION_ROLES, c as ORGANIZATION_ROLE_RANK, O as OrganizationRole, S as SandboxWorkspaceRole, W as WORKSPACE_ROLES, d as WORKSPACE_ROLE_RANK, e as WorkspaceCollaborationAccess, f as WorkspaceRole, g as canManageWorkspaceMemberRole, h as hasOrganizationRole, i as hasWorkspaceRole, j as isAssignableWorkspaceRole, o as organizationRoleGrantsWorkspaceOwner, r as resolveWorkspaceRole, w as workspaceRoleToCollaborationAccess, k as workspaceRoleToSandboxRole } from '../roles-B4nU3jDi.js';
|
|
2
|
+
export { I as INVITATION_EXPIRY_DAYS, a as InvitationEmailBrand, b as InvitationEmailStatus, c as InvitationPermission, d as InvitationStatus, R as RenderInvitationEmailInput, e as RenderedInvitationEmail, g as generateInvitationToken, f as getInvitationExpiresAt, i as inviteUrlForToken, n as normalizeInvitationEmail, p as parseInvitationPermission, r as renderInvitationEmail } from '../invitations-DoYMvXea.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Pure invite-token helpers — generation, shape validation, and expiry math.
|
|
@@ -26,7 +26,9 @@ interface InviteTokenState {
|
|
|
26
26
|
/** Optional hard expiry; omit/undefined for invites that never expire. */
|
|
27
27
|
expiresAt?: Date | number | null;
|
|
28
28
|
}
|
|
29
|
+
/** Define possible reasons for rejecting an invite including acceptance, expiration, or email mismatch */
|
|
29
30
|
type InviteRejectionReason = 'already-accepted' | 'expired' | 'email-mismatch';
|
|
31
|
+
/** Represent the outcome of validating an invite with success status and optional rejection reason */
|
|
30
32
|
interface InviteValidationResult {
|
|
31
33
|
ok: boolean;
|
|
32
34
|
reason?: InviteRejectionReason;
|
package/dist/teams/index.js
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
normalizeInvitationEmail,
|
|
7
7
|
parseInvitationPermission,
|
|
8
8
|
renderInvitationEmail
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-2DRYTJHI.js";
|
|
10
10
|
import {
|
|
11
11
|
generateInviteToken,
|
|
12
12
|
isInviteTokenShape,
|
|
13
13
|
validateInviteToken
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-DJ4VJIH5.js";
|
|
15
15
|
import {
|
|
16
16
|
ASSIGNABLE_WORKSPACE_ROLES,
|
|
17
17
|
ORGANIZATION_ROLES,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolveWorkspaceRole,
|
|
27
27
|
workspaceRoleToCollaborationAccess,
|
|
28
28
|
workspaceRoleToSandboxRole
|
|
29
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-6XIAPIW6.js";
|
|
30
30
|
export {
|
|
31
31
|
ASSIGNABLE_WORKSPACE_ROLES,
|
|
32
32
|
INVITATION_EXPIRY_DAYS,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { c as InvitationPermission, d as InvitationStatus, b as InvitationEmailStatus } from '../invitations-
|
|
1
|
+
import { c as InvitationPermission, d as InvitationStatus, b as InvitationEmailStatus } from '../invitations-DoYMvXea.js';
|
|
2
2
|
import { EnforceSeatSeam, MemberSyncSeam } from './members-api.js';
|
|
3
3
|
export { SeatLimitError } from './members-api.js';
|
|
4
|
-
import { T as TeamDatabase, a as TeamTables, g as TeamParentTable, h as WorkspaceAccessApi } from '../access-
|
|
5
|
-
import { a as WorkspaceInvitationTables } from '../invitations-schema-
|
|
6
|
-
import '../roles-
|
|
4
|
+
import { T as TeamDatabase, a as TeamTables, g as TeamParentTable, h as WorkspaceAccessApi } from '../access-490O6aOU.js';
|
|
5
|
+
import { a as WorkspaceInvitationTables } from '../invitations-schema-_fI7HkJP.js';
|
|
6
|
+
import '../roles-B4nU3jDi.js';
|
|
7
7
|
import 'drizzle-orm/sqlite-core';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -41,12 +41,14 @@ interface SendInvitationEmailInput {
|
|
|
41
41
|
inviteUrl: string;
|
|
42
42
|
expiresAt: Date;
|
|
43
43
|
}
|
|
44
|
+
/** Represent the outcome of sending an invitation email with success status and optional error message */
|
|
44
45
|
type SendInvitationEmailResult = {
|
|
45
46
|
succeeded: true;
|
|
46
47
|
} | {
|
|
47
48
|
succeeded: false;
|
|
48
49
|
error: string;
|
|
49
50
|
};
|
|
51
|
+
/** Resolve sending an invitation email and return the result asynchronously */
|
|
50
52
|
interface SendInvitationEmailSeam {
|
|
51
53
|
(input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>;
|
|
52
54
|
}
|
|
@@ -61,6 +63,7 @@ interface InvitationWorkspaceTable {
|
|
|
61
63
|
id: any;
|
|
62
64
|
name: any;
|
|
63
65
|
}
|
|
66
|
+
/** Define configuration options required to manage workspace invitations and related data sources */
|
|
64
67
|
interface InvitationsApiOptions {
|
|
65
68
|
db: TeamDatabase;
|
|
66
69
|
tables: TeamTables;
|
|
@@ -78,6 +81,7 @@ interface InvitationsApiOptions {
|
|
|
78
81
|
/** Inviter display fallback when the inviter's email is unknown. */
|
|
79
82
|
productDisplayName?: string;
|
|
80
83
|
}
|
|
84
|
+
/** Represent a workspace invitation with details about inviter, permissions, status, and timestamps */
|
|
81
85
|
interface WorkspaceInvitationView {
|
|
82
86
|
id: string;
|
|
83
87
|
workspaceId: string;
|
|
@@ -95,6 +99,7 @@ interface WorkspaceInvitationView {
|
|
|
95
99
|
revokedAt: Date | null;
|
|
96
100
|
lastSentAt: Date | null;
|
|
97
101
|
}
|
|
102
|
+
/** Describe the structure of an invitation preview with workspace, email, permissions, status, and expiration details */
|
|
98
103
|
interface InvitationPreview {
|
|
99
104
|
workspaceId: string;
|
|
100
105
|
workspaceName: string;
|
|
@@ -104,6 +109,7 @@ interface InvitationPreview {
|
|
|
104
109
|
status: InvitationStatus;
|
|
105
110
|
expiresAt: Date;
|
|
106
111
|
}
|
|
112
|
+
/** Resolve the result of an invitation as success with a value or failure with status and error details */
|
|
107
113
|
type InvitationOutcome<T> = {
|
|
108
114
|
succeeded: true;
|
|
109
115
|
value: T;
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
inviteUrlForToken,
|
|
5
5
|
normalizeInvitationEmail,
|
|
6
6
|
parseInvitationPermission
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-2DRYTJHI.js";
|
|
8
8
|
import {
|
|
9
9
|
SeatLimitError
|
|
10
|
-
} from "../chunk-
|
|
11
|
-
import "../chunk-
|
|
10
|
+
} from "../chunk-MEUNTJL5.js";
|
|
11
|
+
import "../chunk-DJ4VJIH5.js";
|
|
12
12
|
import {
|
|
13
13
|
hasWorkspaceRole
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-6XIAPIW6.js";
|
|
15
15
|
|
|
16
16
|
// src/teams/invitations-api.ts
|
|
17
17
|
import { and, eq, lte, sql } from "drizzle-orm";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/teams/invitations-api.ts"],"sourcesContent":["/**\n * Framework-neutral email-invitation lifecycle for the teams module: create /\n * list / resend / revoke / preview / accept over the dedicated\n * `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out\n * of any one app's route file — each app binds this once to its own db/tables/\n * access and mounts the handlers in its own routes with its own auth.\n *\n * Unlike `members-api` (whose \"invite\" is a pending workspaceMember row, no\n * email), this models the rich lifecycle: a dedicated invitation row with status,\n * 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the\n * `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`\n * still surfaces accepted members; pending invites live only here.\n *\n * Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the\n * route adapter maps `{ status }` to its own framework's response. Three seams:\n * - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a\n * typed outcome; a failed send never blocks invitation creation (emailStatus\n * is recorded as 'failed' and the invite link is still returned).\n * - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time\n * only when the invite would consume a NEW seat. Reused from members-api.\n * - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new\n * member to a sandbox/external system on accept. Fail-soft by contract.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, lte, sql } from 'drizzle-orm'\nimport {\n type InvitationEmailStatus,\n type InvitationPermission,\n type InvitationStatus,\n generateInvitationToken,\n getInvitationExpiresAt,\n inviteUrlForToken,\n normalizeInvitationEmail,\n parseInvitationPermission,\n} from './invitations'\nimport { type WorkspaceRole, hasWorkspaceRole } from './roles'\nimport { type EnforceSeatSeam, type MemberSyncSeam, SeatLimitError } from './members-api'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\nimport type { WorkspaceInvitationRow, WorkspaceInvitationTables } from './drizzle/invitations-schema'\n\n// Re-export the seams an adopter wires, so a consumer can import everything the\n// invitations API needs from this one subpath.\nexport { SeatLimitError } from './members-api'\nexport type { EnforceSeatSeam, MemberSyncSeam } from './members-api'\n\n/** The app's mail transport. Returns a typed outcome; never throws to the API. */\nexport interface SendInvitationEmailInput {\n to: string\n workspaceName: string\n inviterEmail: string\n permission: InvitationPermission\n inviteUrl: string\n expiresAt: Date\n}\nexport type SendInvitationEmailResult = { succeeded: true } | { succeeded: false; error: string }\nexport interface SendInvitationEmailSeam {\n (input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>\n}\n\n/** The product's user table, narrowed to the columns the queries read. */\nexport interface InvitationUserTable {\n id: any\n email: any\n emailVerified: any\n}\n\n/** The product's workspace table, narrowed to the columns the queries read. */\nexport interface InvitationWorkspaceTable {\n id: any\n name: any\n}\n\nexport interface InvitationsApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The invitation table from `createWorkspaceInvitationTable`. */\n invitationsTable: WorkspaceInvitationTables\n userTable: TeamParentTable & InvitationUserTable\n workspaceTable: TeamParentTable & InvitationWorkspaceTable\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n /** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */\n sendInvitationEmail: SendInvitationEmailSeam\n /** OPTIONAL — seat-limit gate at create time; mirrors members-api. */\n enforceSeat?: EnforceSeatSeam\n /** OPTIONAL — fire-and-forget member propagation on accept. */\n memberSyncSeam?: Pick<MemberSyncSeam, 'add'>\n /** Inviter display fallback when the inviter's email is unknown. */\n productDisplayName?: string\n}\n\nexport interface WorkspaceInvitationView {\n id: string\n workspaceId: string\n organizationId: string\n email: string\n invitedByUserId: string\n inviterEmail: string | null\n permissions: InvitationPermission\n token: string\n status: InvitationStatus\n emailStatus: InvitationEmailStatus\n expiresAt: Date\n createdAt: Date\n acceptedAt: Date | null\n revokedAt: Date | null\n lastSentAt: Date | null\n}\n\nexport interface InvitationPreview {\n workspaceId: string\n workspaceName: string\n email: string\n inviterEmail: string | null\n permissions: InvitationPermission\n status: InvitationStatus\n expiresAt: Date\n}\n\nexport type InvitationOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; error: string }\n\n/**\n * Build the invitations API bound to one product's db/tables/access/seams.\n * Returns the six lifecycle handlers an app mounts in its routes.\n */\nexport function createInvitationsApi(opts: InvitationsApiOptions) {\n const { db, tables, access, sendInvitationEmail, enforceSeat, memberSyncSeam } = opts\n const { organizationMembers, workspaceMembers } = tables\n const { workspaceInvitations } = opts.invitationsTable\n const users = opts.userTable\n const workspaces = opts.workspaceTable\n const productDisplayName = opts.productDisplayName ?? 'A workspace admin'\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function createInvitation(input: {\n workspaceId: string\n email: string\n permissions: string | undefined\n invitedByUserId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const now = input.now ?? new Date()\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.invitedByUserId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const permissions = parseInvitationPermission(input.permissions ?? 'editor')\n if (!permissions) return { succeeded: false, status: 400, error: 'Invalid permissions' }\n if (!hasWorkspaceRole(accessRow.role, permissions)) {\n return { succeeded: false, status: 403, error: 'Cannot assign higher permissions than your own' }\n }\n\n const email = normalizeInvitationEmail(input.email)\n if (!email) return { succeeded: false, status: 400, error: 'Missing email' }\n\n const organizationId = accessRow.organization.id\n const workspaceName = (accessRow.workspace as { name: string }).name\n\n await expirePendingInvitations(input.workspaceId, email, now)\n\n const existingPending = await getPendingInvitation(input.workspaceId, email)\n if (existingPending) return { succeeded: false, status: 409, error: 'A pending invitation already exists for this email' }\n\n const accountCheck = await checkExistingAccountAccess(organizationId, input.workspaceId, email, input.invitedByUserId)\n if (!accountCheck.succeeded) return accountCheck\n\n // Seat gate: only a genuinely NEW seat. The checks above reject re-invites,\n // existing workspace members, and org admins; an existing plain org member is\n // already billable (no new seat), so skip the gate for them — mirrors\n // members-api. The pending row is not inserted yet, so the seam's count\n // reflects current usage and denies the invite that would push OVER the limit\n // (at invite time, not accept time).\n if (enforceSeat && !accountCheck.isExistingOrgMember) {\n try {\n await enforceSeat({ actorId: input.invitedByUserId, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) return { succeeded: false, status: err.status, error: err.message }\n throw err\n }\n }\n\n const token = generateInvitationToken()\n const expiresAt = getInvitationExpiresAt(now)\n\n const [created] = await db.insert(workspaceInvitations).values({\n workspaceId: input.workspaceId,\n organizationId,\n email,\n invitedByUserId: input.invitedByUserId,\n permissions,\n token,\n status: 'pending',\n emailStatus: 'not_sent',\n expiresAt,\n createdAt: now,\n }).returning()\n\n const inviteUrl = inviteUrlForToken(input.origin, token)\n const emailResult = await sendInvitationEmail({\n to: email,\n workspaceName,\n inviterEmail: accountCheck.inviterEmail ?? productDisplayName,\n permission: permissions,\n inviteUrl,\n expiresAt,\n })\n\n const emailStatus: InvitationEmailStatus = emailResult.succeeded ? 'sent' : 'failed'\n const [updated] = await db.update(workspaceInvitations)\n .set({ emailStatus, lastSentAt: emailResult.succeeded ? now : null })\n .where(eq(workspaceInvitations.id, created!.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, accountCheck.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function listInvitations(input: {\n workspaceId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitations: Array<WorkspaceInvitationView & { inviteUrl: string }> }>> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n await expirePendingInvitations(input.workspaceId, undefined, input.now ?? new Date())\n\n const rows = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.workspaceId, input.workspaceId))\n .orderBy(sql`${workspaceInvitations.createdAt} desc`)\n\n return {\n succeeded: true,\n value: {\n invitations: rows.map((row: { invitation: WorkspaceInvitationRow; inviterEmail: string | null }) => ({\n ...toInvitationView(row.invitation, row.inviterEmail),\n inviteUrl: inviteUrlForToken(input.origin, row.invitation.token),\n })),\n },\n }\n }\n\n async function resendInvitation(input: {\n invitationId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const now = input.now ?? new Date()\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be resent' }\n }\n if (row.invitation.expiresAt <= now) {\n await markInvitationExpired(row.invitation.id)\n return { succeeded: false, status: 409, error: 'Invitation has expired' }\n }\n\n const workspaceName = (accessRow.workspace as { name: string }).name\n const inviteUrl = inviteUrlForToken(input.origin, row.invitation.token)\n const emailResult = await sendInvitationEmail({\n to: row.invitation.email,\n workspaceName,\n inviterEmail: row.inviterEmail ?? productDisplayName,\n permission: row.invitation.permissions,\n inviteUrl,\n expiresAt: row.invitation.expiresAt,\n })\n\n const [updated] = await db.update(workspaceInvitations)\n .set({\n emailStatus: emailResult.succeeded ? 'sent' : 'failed',\n lastSentAt: emailResult.succeeded ? now : row.invitation.lastSentAt,\n })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, row.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function revokeInvitation(input: {\n invitationId: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be revoked' }\n }\n\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'revoked', revokedAt: input.now ?? new Date() })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return { succeeded: true, value: { invitation: toInvitationView(updated!, row.inviterEmail) } }\n }\n\n async function getPreview(token: string, now: Date = new Date()): Promise<InvitationOutcome<InvitationPreview>> {\n const row = await getInvitationByToken(token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n return {\n succeeded: true,\n value: {\n workspaceId: invitation.workspaceId,\n workspaceName: row.workspaceName,\n email: invitation.email,\n inviterEmail: row.inviterEmail,\n permissions: invitation.permissions,\n status: invitation.status,\n expiresAt: invitation.expiresAt,\n },\n }\n }\n\n async function acceptInvitation(input: {\n token: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ workspaceId: string }>> {\n const now = input.now ?? new Date()\n const row = await getInvitationByToken(input.token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n const [currentUser] = await db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(eq(users.id, input.userId))\n .limit(1)\n if (!currentUser) return { succeeded: false, status: 401, error: 'Authentication required' }\n\n if (normalizeInvitationEmail(currentUser.email) !== invitation.email) {\n return { succeeded: false, status: 403, error: 'Sign in with the invited email address to accept this invitation' }\n }\n\n const collisionCheck = await findUsersByNormalizedEmail(invitation.email)\n if (collisionCheck.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before accepting this invitation.',\n }\n }\n\n let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n\n if (invitation.status === 'accepted') {\n const existing = orgMember ? await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id) : null\n if (existing) return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n return { succeeded: false, status: 409, error: 'Invitation has already been accepted' }\n }\n if (invitation.status === 'expired') return { succeeded: false, status: 409, error: 'Invitation has expired' }\n if (invitation.status === 'revoked') return { succeeded: false, status: 409, error: 'Invitation has been revoked' }\n\n if (!orgMember) {\n const [inserted] = await db.insert(organizationMembers).values({\n organizationId: invitation.organizationId,\n userId: currentUser.id,\n role: 'member',\n createdAt: now,\n updatedAt: now,\n }).returning()\n if (!inserted) return { succeeded: false, status: 500, error: 'Failed to create organization membership' }\n orgMember = inserted\n }\n\n const existingMember = await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id)\n if (!existingMember) {\n await db.insert(workspaceMembers).values({\n workspaceId: invitation.workspaceId,\n organizationMemberId: orgMember.id,\n userId: currentUser.id,\n role: invitation.permissions,\n inviteEmail: invitation.email,\n // members-api nulls the token on accept to release the unique slot; the\n // invitations flow keeps it (its `inv_` tokens never collide), so the\n // materialized row carries the originating token for audit.\n inviteToken: invitation.token,\n invitedAt: now,\n acceptedAt: now,\n })\n if (memberSyncSeam?.add) {\n const userId = currentUser.id\n const role = invitation.permissions as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invitation.workspaceId, userId, role }))\n }\n }\n\n await db.update(workspaceInvitations)\n .set({ status: 'accepted', acceptedAt: now })\n .where(eq(workspaceInvitations.id, invitation.id))\n\n return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n }\n\n // ── internal query helpers ──\n\n async function expirePendingInvitations(workspaceId: string, email: string | undefined, now: Date) {\n const conditions = [\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.status, 'pending' as const),\n lte(workspaceInvitations.expiresAt, now),\n ]\n if (email) conditions.push(eq(workspaceInvitations.email, email))\n await db.update(workspaceInvitations).set({ status: 'expired' }).where(and(...conditions))\n }\n\n async function getPendingInvitation(workspaceId: string, email: string) {\n const [invitation] = await db\n .select({ id: workspaceInvitations.id })\n .from(workspaceInvitations)\n .where(and(\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.email, email),\n eq(workspaceInvitations.status, 'pending' as const),\n ))\n .limit(1)\n return invitation ?? null\n }\n\n async function checkExistingAccountAccess(\n organizationId: string,\n workspaceId: string,\n email: string,\n invitedByUserId: string,\n ): Promise<\n | { succeeded: false; status: number; error: string }\n | { succeeded: true; inviterEmail: string | null; isExistingOrgMember: boolean }\n > {\n const matchingUsers = await findUsersByNormalizedEmail(email)\n const [inviter] = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, invitedByUserId))\n .limit(1)\n\n if (matchingUsers.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before inviting this user.',\n }\n }\n const existingUser = matchingUsers[0]\n if (!existingUser) return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: false }\n\n const orgMember = await getOrganizationMember(organizationId, existingUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n if (orgMember) {\n const workspaceMember = await getWorkspaceMemberByOrganizationMember(workspaceId, orgMember.id)\n if (workspaceMember) return { succeeded: false, status: 409, error: 'User is already a workspace member' }\n }\n\n return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: Boolean(orgMember) }\n }\n\n async function findUsersByNormalizedEmail(email: string) {\n return db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(sql`lower(${users.email}) = ${email}`)\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function getWorkspaceMemberByOrganizationMember(workspaceId: string, organizationMemberId: string) {\n const [member] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(\n eq(workspaceMembers.workspaceId, workspaceId),\n eq(workspaceMembers.organizationMemberId, organizationMemberId),\n ))\n .limit(1)\n return member ?? null\n }\n\n async function getInvitationById(invitationId: string) {\n const [row] = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.id, invitationId))\n .limit(1)\n return row ?? null\n }\n\n async function getInvitationByToken(token: string) {\n const [row] = await db\n .select({\n invitation: workspaceInvitations,\n workspaceName: workspaces.name,\n inviterEmail: users.email,\n })\n .from(workspaceInvitations)\n .innerJoin(workspaces, eq(workspaces.id, workspaceInvitations.workspaceId))\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.token, token))\n .limit(1)\n return row ?? null\n }\n\n async function markInvitationExpired(invitationId: string) {\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'expired' })\n .where(eq(workspaceInvitations.id, invitationId))\n .returning()\n return updated!\n }\n\n function toInvitationView(invitation: WorkspaceInvitationRow, inviterEmail: string | null): WorkspaceInvitationView {\n return {\n id: invitation.id,\n workspaceId: invitation.workspaceId,\n organizationId: invitation.organizationId,\n email: invitation.email,\n invitedByUserId: invitation.invitedByUserId,\n inviterEmail,\n permissions: invitation.permissions,\n token: invitation.token,\n status: invitation.status,\n emailStatus: invitation.emailStatus,\n expiresAt: invitation.expiresAt,\n createdAt: invitation.createdAt,\n acceptedAt: invitation.acceptedAt,\n revokedAt: invitation.revokedAt,\n lastSentAt: invitation.lastSentAt,\n }\n }\n\n return { createInvitation, listInvitations, resendInvitation, revokeInvitation, getPreview, acceptInvitation }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/teams/invitations-api.ts"],"sourcesContent":["/**\n * Framework-neutral email-invitation lifecycle for the teams module: create /\n * list / resend / revoke / preview / accept over the dedicated\n * `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out\n * of any one app's route file — each app binds this once to its own db/tables/\n * access and mounts the handlers in its own routes with its own auth.\n *\n * Unlike `members-api` (whose \"invite\" is a pending workspaceMember row, no\n * email), this models the rich lifecycle: a dedicated invitation row with status,\n * 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the\n * `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`\n * still surfaces accepted members; pending invites live only here.\n *\n * Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the\n * route adapter maps `{ status }` to its own framework's response. Three seams:\n * - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a\n * typed outcome; a failed send never blocks invitation creation (emailStatus\n * is recorded as 'failed' and the invite link is still returned).\n * - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time\n * only when the invite would consume a NEW seat. Reused from members-api.\n * - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new\n * member to a sandbox/external system on accept. Fail-soft by contract.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, lte, sql } from 'drizzle-orm'\nimport {\n type InvitationEmailStatus,\n type InvitationPermission,\n type InvitationStatus,\n generateInvitationToken,\n getInvitationExpiresAt,\n inviteUrlForToken,\n normalizeInvitationEmail,\n parseInvitationPermission,\n} from './invitations'\nimport { type WorkspaceRole, hasWorkspaceRole } from './roles'\nimport { type EnforceSeatSeam, type MemberSyncSeam, SeatLimitError } from './members-api'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\nimport type { WorkspaceInvitationRow, WorkspaceInvitationTables } from './drizzle/invitations-schema'\n\n// Re-export the seams an adopter wires, so a consumer can import everything the\n// invitations API needs from this one subpath.\nexport { SeatLimitError } from './members-api'\nexport type { EnforceSeatSeam, MemberSyncSeam } from './members-api'\n\n/** The app's mail transport. Returns a typed outcome; never throws to the API. */\nexport interface SendInvitationEmailInput {\n to: string\n workspaceName: string\n inviterEmail: string\n permission: InvitationPermission\n inviteUrl: string\n expiresAt: Date\n}\n/** Represent the outcome of sending an invitation email with success status and optional error message */\nexport type SendInvitationEmailResult = { succeeded: true } | { succeeded: false; error: string }\n/** Resolve sending an invitation email and return the result asynchronously */\nexport interface SendInvitationEmailSeam {\n (input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>\n}\n\n/** The product's user table, narrowed to the columns the queries read. */\nexport interface InvitationUserTable {\n id: any\n email: any\n emailVerified: any\n}\n\n/** The product's workspace table, narrowed to the columns the queries read. */\nexport interface InvitationWorkspaceTable {\n id: any\n name: any\n}\n\n/** Define configuration options required to manage workspace invitations and related data sources */\nexport interface InvitationsApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The invitation table from `createWorkspaceInvitationTable`. */\n invitationsTable: WorkspaceInvitationTables\n userTable: TeamParentTable & InvitationUserTable\n workspaceTable: TeamParentTable & InvitationWorkspaceTable\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n /** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */\n sendInvitationEmail: SendInvitationEmailSeam\n /** OPTIONAL — seat-limit gate at create time; mirrors members-api. */\n enforceSeat?: EnforceSeatSeam\n /** OPTIONAL — fire-and-forget member propagation on accept. */\n memberSyncSeam?: Pick<MemberSyncSeam, 'add'>\n /** Inviter display fallback when the inviter's email is unknown. */\n productDisplayName?: string\n}\n\n/** Represent a workspace invitation with details about inviter, permissions, status, and timestamps */\nexport interface WorkspaceInvitationView {\n id: string\n workspaceId: string\n organizationId: string\n email: string\n invitedByUserId: string\n inviterEmail: string | null\n permissions: InvitationPermission\n token: string\n status: InvitationStatus\n emailStatus: InvitationEmailStatus\n expiresAt: Date\n createdAt: Date\n acceptedAt: Date | null\n revokedAt: Date | null\n lastSentAt: Date | null\n}\n\n/** Describe the structure of an invitation preview with workspace, email, permissions, status, and expiration details */\nexport interface InvitationPreview {\n workspaceId: string\n workspaceName: string\n email: string\n inviterEmail: string | null\n permissions: InvitationPermission\n status: InvitationStatus\n expiresAt: Date\n}\n\n/** Resolve the result of an invitation as success with a value or failure with status and error details */\nexport type InvitationOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; error: string }\n\n/**\n * Build the invitations API bound to one product's db/tables/access/seams.\n * Returns the six lifecycle handlers an app mounts in its routes.\n */\nexport function createInvitationsApi(opts: InvitationsApiOptions) {\n const { db, tables, access, sendInvitationEmail, enforceSeat, memberSyncSeam } = opts\n const { organizationMembers, workspaceMembers } = tables\n const { workspaceInvitations } = opts.invitationsTable\n const users = opts.userTable\n const workspaces = opts.workspaceTable\n const productDisplayName = opts.productDisplayName ?? 'A workspace admin'\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function createInvitation(input: {\n workspaceId: string\n email: string\n permissions: string | undefined\n invitedByUserId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const now = input.now ?? new Date()\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.invitedByUserId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const permissions = parseInvitationPermission(input.permissions ?? 'editor')\n if (!permissions) return { succeeded: false, status: 400, error: 'Invalid permissions' }\n if (!hasWorkspaceRole(accessRow.role, permissions)) {\n return { succeeded: false, status: 403, error: 'Cannot assign higher permissions than your own' }\n }\n\n const email = normalizeInvitationEmail(input.email)\n if (!email) return { succeeded: false, status: 400, error: 'Missing email' }\n\n const organizationId = accessRow.organization.id\n const workspaceName = (accessRow.workspace as { name: string }).name\n\n await expirePendingInvitations(input.workspaceId, email, now)\n\n const existingPending = await getPendingInvitation(input.workspaceId, email)\n if (existingPending) return { succeeded: false, status: 409, error: 'A pending invitation already exists for this email' }\n\n const accountCheck = await checkExistingAccountAccess(organizationId, input.workspaceId, email, input.invitedByUserId)\n if (!accountCheck.succeeded) return accountCheck\n\n // Seat gate: only a genuinely NEW seat. The checks above reject re-invites,\n // existing workspace members, and org admins; an existing plain org member is\n // already billable (no new seat), so skip the gate for them — mirrors\n // members-api. The pending row is not inserted yet, so the seam's count\n // reflects current usage and denies the invite that would push OVER the limit\n // (at invite time, not accept time).\n if (enforceSeat && !accountCheck.isExistingOrgMember) {\n try {\n await enforceSeat({ actorId: input.invitedByUserId, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) return { succeeded: false, status: err.status, error: err.message }\n throw err\n }\n }\n\n const token = generateInvitationToken()\n const expiresAt = getInvitationExpiresAt(now)\n\n const [created] = await db.insert(workspaceInvitations).values({\n workspaceId: input.workspaceId,\n organizationId,\n email,\n invitedByUserId: input.invitedByUserId,\n permissions,\n token,\n status: 'pending',\n emailStatus: 'not_sent',\n expiresAt,\n createdAt: now,\n }).returning()\n\n const inviteUrl = inviteUrlForToken(input.origin, token)\n const emailResult = await sendInvitationEmail({\n to: email,\n workspaceName,\n inviterEmail: accountCheck.inviterEmail ?? productDisplayName,\n permission: permissions,\n inviteUrl,\n expiresAt,\n })\n\n const emailStatus: InvitationEmailStatus = emailResult.succeeded ? 'sent' : 'failed'\n const [updated] = await db.update(workspaceInvitations)\n .set({ emailStatus, lastSentAt: emailResult.succeeded ? now : null })\n .where(eq(workspaceInvitations.id, created!.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, accountCheck.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function listInvitations(input: {\n workspaceId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitations: Array<WorkspaceInvitationView & { inviteUrl: string }> }>> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n await expirePendingInvitations(input.workspaceId, undefined, input.now ?? new Date())\n\n const rows = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.workspaceId, input.workspaceId))\n .orderBy(sql`${workspaceInvitations.createdAt} desc`)\n\n return {\n succeeded: true,\n value: {\n invitations: rows.map((row: { invitation: WorkspaceInvitationRow; inviterEmail: string | null }) => ({\n ...toInvitationView(row.invitation, row.inviterEmail),\n inviteUrl: inviteUrlForToken(input.origin, row.invitation.token),\n })),\n },\n }\n }\n\n async function resendInvitation(input: {\n invitationId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const now = input.now ?? new Date()\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be resent' }\n }\n if (row.invitation.expiresAt <= now) {\n await markInvitationExpired(row.invitation.id)\n return { succeeded: false, status: 409, error: 'Invitation has expired' }\n }\n\n const workspaceName = (accessRow.workspace as { name: string }).name\n const inviteUrl = inviteUrlForToken(input.origin, row.invitation.token)\n const emailResult = await sendInvitationEmail({\n to: row.invitation.email,\n workspaceName,\n inviterEmail: row.inviterEmail ?? productDisplayName,\n permission: row.invitation.permissions,\n inviteUrl,\n expiresAt: row.invitation.expiresAt,\n })\n\n const [updated] = await db.update(workspaceInvitations)\n .set({\n emailStatus: emailResult.succeeded ? 'sent' : 'failed',\n lastSentAt: emailResult.succeeded ? now : row.invitation.lastSentAt,\n })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, row.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function revokeInvitation(input: {\n invitationId: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be revoked' }\n }\n\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'revoked', revokedAt: input.now ?? new Date() })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return { succeeded: true, value: { invitation: toInvitationView(updated!, row.inviterEmail) } }\n }\n\n async function getPreview(token: string, now: Date = new Date()): Promise<InvitationOutcome<InvitationPreview>> {\n const row = await getInvitationByToken(token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n return {\n succeeded: true,\n value: {\n workspaceId: invitation.workspaceId,\n workspaceName: row.workspaceName,\n email: invitation.email,\n inviterEmail: row.inviterEmail,\n permissions: invitation.permissions,\n status: invitation.status,\n expiresAt: invitation.expiresAt,\n },\n }\n }\n\n async function acceptInvitation(input: {\n token: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ workspaceId: string }>> {\n const now = input.now ?? new Date()\n const row = await getInvitationByToken(input.token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n const [currentUser] = await db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(eq(users.id, input.userId))\n .limit(1)\n if (!currentUser) return { succeeded: false, status: 401, error: 'Authentication required' }\n\n if (normalizeInvitationEmail(currentUser.email) !== invitation.email) {\n return { succeeded: false, status: 403, error: 'Sign in with the invited email address to accept this invitation' }\n }\n\n const collisionCheck = await findUsersByNormalizedEmail(invitation.email)\n if (collisionCheck.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before accepting this invitation.',\n }\n }\n\n let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n\n if (invitation.status === 'accepted') {\n const existing = orgMember ? await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id) : null\n if (existing) return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n return { succeeded: false, status: 409, error: 'Invitation has already been accepted' }\n }\n if (invitation.status === 'expired') return { succeeded: false, status: 409, error: 'Invitation has expired' }\n if (invitation.status === 'revoked') return { succeeded: false, status: 409, error: 'Invitation has been revoked' }\n\n if (!orgMember) {\n const [inserted] = await db.insert(organizationMembers).values({\n organizationId: invitation.organizationId,\n userId: currentUser.id,\n role: 'member',\n createdAt: now,\n updatedAt: now,\n }).returning()\n if (!inserted) return { succeeded: false, status: 500, error: 'Failed to create organization membership' }\n orgMember = inserted\n }\n\n const existingMember = await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id)\n if (!existingMember) {\n await db.insert(workspaceMembers).values({\n workspaceId: invitation.workspaceId,\n organizationMemberId: orgMember.id,\n userId: currentUser.id,\n role: invitation.permissions,\n inviteEmail: invitation.email,\n // members-api nulls the token on accept to release the unique slot; the\n // invitations flow keeps it (its `inv_` tokens never collide), so the\n // materialized row carries the originating token for audit.\n inviteToken: invitation.token,\n invitedAt: now,\n acceptedAt: now,\n })\n if (memberSyncSeam?.add) {\n const userId = currentUser.id\n const role = invitation.permissions as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invitation.workspaceId, userId, role }))\n }\n }\n\n await db.update(workspaceInvitations)\n .set({ status: 'accepted', acceptedAt: now })\n .where(eq(workspaceInvitations.id, invitation.id))\n\n return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n }\n\n // ── internal query helpers ──\n\n async function expirePendingInvitations(workspaceId: string, email: string | undefined, now: Date) {\n const conditions = [\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.status, 'pending' as const),\n lte(workspaceInvitations.expiresAt, now),\n ]\n if (email) conditions.push(eq(workspaceInvitations.email, email))\n await db.update(workspaceInvitations).set({ status: 'expired' }).where(and(...conditions))\n }\n\n async function getPendingInvitation(workspaceId: string, email: string) {\n const [invitation] = await db\n .select({ id: workspaceInvitations.id })\n .from(workspaceInvitations)\n .where(and(\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.email, email),\n eq(workspaceInvitations.status, 'pending' as const),\n ))\n .limit(1)\n return invitation ?? null\n }\n\n async function checkExistingAccountAccess(\n organizationId: string,\n workspaceId: string,\n email: string,\n invitedByUserId: string,\n ): Promise<\n | { succeeded: false; status: number; error: string }\n | { succeeded: true; inviterEmail: string | null; isExistingOrgMember: boolean }\n > {\n const matchingUsers = await findUsersByNormalizedEmail(email)\n const [inviter] = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, invitedByUserId))\n .limit(1)\n\n if (matchingUsers.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before inviting this user.',\n }\n }\n const existingUser = matchingUsers[0]\n if (!existingUser) return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: false }\n\n const orgMember = await getOrganizationMember(organizationId, existingUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n if (orgMember) {\n const workspaceMember = await getWorkspaceMemberByOrganizationMember(workspaceId, orgMember.id)\n if (workspaceMember) return { succeeded: false, status: 409, error: 'User is already a workspace member' }\n }\n\n return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: Boolean(orgMember) }\n }\n\n async function findUsersByNormalizedEmail(email: string) {\n return db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(sql`lower(${users.email}) = ${email}`)\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function getWorkspaceMemberByOrganizationMember(workspaceId: string, organizationMemberId: string) {\n const [member] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(\n eq(workspaceMembers.workspaceId, workspaceId),\n eq(workspaceMembers.organizationMemberId, organizationMemberId),\n ))\n .limit(1)\n return member ?? null\n }\n\n async function getInvitationById(invitationId: string) {\n const [row] = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.id, invitationId))\n .limit(1)\n return row ?? null\n }\n\n async function getInvitationByToken(token: string) {\n const [row] = await db\n .select({\n invitation: workspaceInvitations,\n workspaceName: workspaces.name,\n inviterEmail: users.email,\n })\n .from(workspaceInvitations)\n .innerJoin(workspaces, eq(workspaces.id, workspaceInvitations.workspaceId))\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.token, token))\n .limit(1)\n return row ?? null\n }\n\n async function markInvitationExpired(invitationId: string) {\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'expired' })\n .where(eq(workspaceInvitations.id, invitationId))\n .returning()\n return updated!\n }\n\n function toInvitationView(invitation: WorkspaceInvitationRow, inviterEmail: string | null): WorkspaceInvitationView {\n return {\n id: invitation.id,\n workspaceId: invitation.workspaceId,\n organizationId: invitation.organizationId,\n email: invitation.email,\n invitedByUserId: invitation.invitedByUserId,\n inviterEmail,\n permissions: invitation.permissions,\n token: invitation.token,\n status: invitation.status,\n emailStatus: invitation.emailStatus,\n expiresAt: invitation.expiresAt,\n createdAt: invitation.createdAt,\n acceptedAt: invitation.acceptedAt,\n revokedAt: invitation.revokedAt,\n lastSentAt: invitation.lastSentAt,\n }\n }\n\n return { createInvitation, listInvitations, resendInvitation, revokeInvitation, getPreview, acceptInvitation }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { f as WorkspaceRole } from '../roles-
|
|
2
|
-
import { T as TeamDatabase, a as TeamTables, g as TeamParentTable, h as WorkspaceAccessApi } from '../access-
|
|
1
|
+
import { f as WorkspaceRole } from '../roles-B4nU3jDi.js';
|
|
2
|
+
import { T as TeamDatabase, a as TeamTables, g as TeamParentTable, h as WorkspaceAccessApi } from '../access-490O6aOU.js';
|
|
3
3
|
import 'drizzle-orm/sqlite-core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -93,6 +93,7 @@ interface MemberSyncSeam {
|
|
|
93
93
|
userId: string;
|
|
94
94
|
}): Promise<void> | void;
|
|
95
95
|
}
|
|
96
|
+
/** Define configuration options for managing team members and workspace access APIs */
|
|
96
97
|
interface MembersApiOptions {
|
|
97
98
|
db: TeamDatabase;
|
|
98
99
|
tables: TeamTables;
|
|
@@ -105,6 +106,7 @@ interface MembersApiOptions {
|
|
|
105
106
|
enforceSeat?: EnforceSeatSeam;
|
|
106
107
|
memberSyncSeam?: MemberSyncSeam;
|
|
107
108
|
}
|
|
109
|
+
/** Define the structure of a workspace member entry with identification, role, and status details */
|
|
108
110
|
interface MemberListEntry {
|
|
109
111
|
id: string;
|
|
110
112
|
userId: string | null;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SeatLimitError,
|
|
3
3
|
createMembersApi
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-MEUNTJL5.js";
|
|
5
|
+
import "../chunk-DJ4VJIH5.js";
|
|
6
|
+
import "../chunk-6XIAPIW6.js";
|
|
7
7
|
export {
|
|
8
8
|
SeatLimitError,
|
|
9
9
|
createMembersApi
|
package/dist/teams/resend.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SendInvitationEmailSeam } from './invitations-api.js';
|
|
2
|
-
import '../invitations-
|
|
3
|
-
import '../roles-
|
|
2
|
+
import '../invitations-DoYMvXea.js';
|
|
3
|
+
import '../roles-B4nU3jDi.js';
|
|
4
4
|
import './members-api.js';
|
|
5
|
-
import '../access-
|
|
5
|
+
import '../access-490O6aOU.js';
|
|
6
6
|
import 'drizzle-orm/sqlite-core';
|
|
7
|
-
import '../invitations-schema-
|
|
7
|
+
import '../invitations-schema-_fI7HkJP.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Opt-in Resend transport for the teams invitations `sendInvitationEmail` seam.
|
|
@@ -19,6 +19,7 @@ import '../invitations-schema-DaMO4hZE.js';
|
|
|
19
19
|
* passing a raw seam, and the package core never pulls a mail dependency.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
/** Define options for sending a resend invitation including sender address and optional API key */
|
|
22
23
|
interface ResendInvitationSenderOptions {
|
|
23
24
|
/** RFC-5322 From header, e.g. `GTM Agent <noreply@gtm.tangle.tools>`. */
|
|
24
25
|
from: string;
|
package/dist/teams/resend.js
CHANGED
package/dist/teams/resend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/teams/resend.ts"],"sourcesContent":["/**\n * Opt-in Resend transport for the teams invitations `sendInvitationEmail` seam.\n * Most adopters back the seam with Resend and hand-roll the same wrapper — and\n * the same mistake: `resend.emails.send()` returns `{ data, error }` and does NOT\n * throw on an API-level failure (unverified domain, rate limit, bad recipient),\n * so a `try/catch` alone records a failed send as a success. This helper sends\n * through the shared `renderInvitationEmail` template and returns a typed failure\n * on BOTH a thrown error AND a non-null `result.error`.\n *\n * `resend` is an OPTIONAL peer, imported only here: apps not on Resend keep\n * passing a raw seam, and the package core never pulls a mail dependency.\n */\n\nimport { Resend } from 'resend'\nimport { renderInvitationEmail } from './invitations'\nimport type { SendInvitationEmailSeam } from './invitations-api'\n\nexport interface ResendInvitationSenderOptions {\n /** RFC-5322 From header, e.g. `GTM Agent <noreply@gtm.tangle.tools>`. */\n from: string\n /** Resend API key. Defaults to `process.env.RESEND_API_KEY`. */\n apiKey?: string\n}\n\n/**\n * Build a `sendInvitationEmail` seam backed by Resend. Wire it into\n * `createInvitationsApi({ sendInvitationEmail: createResendInvitationSender({ from }) })`.\n * The client is built lazily on first send; with no key the seam fails typed\n * (the invitation is still created — emailStatus becomes 'failed').\n */\nexport function createResendInvitationSender(opts: ResendInvitationSenderOptions): SendInvitationEmailSeam {\n let client: Resend | null = null\n\n function getClient(): Resend | null {\n if (client) return client\n const key = opts.apiKey ?? (typeof process !== 'undefined' ? process.env.RESEND_API_KEY : undefined)\n if (!key) return null\n client = new Resend(key)\n return client\n }\n\n return async (input) => {\n const resend = getClient()\n if (!resend) return { succeeded: false, error: 'RESEND_API_KEY is not configured' }\n\n const msg = renderInvitationEmail(input, { fromAddress: opts.from })\n try {\n const result = await resend.emails.send({\n from: msg.from,\n to: input.to,\n subject: msg.subject,\n html: msg.html,\n text: msg.text,\n })\n // Resend reports API-level failures in `result.error` WITHOUT throwing — a\n // try/catch alone would mark a failed send as a success.\n if (result.error) return { succeeded: false, error: result.error.message }\n return { succeeded: true }\n } catch (err) {\n return { succeeded: false, error: err instanceof Error ? err.message : 'Invitation email failed to send' }\n }\n }\n}\n"],"mappings":";;;;;AAaA,SAAS,cAAc;
|
|
1
|
+
{"version":3,"sources":["../../src/teams/resend.ts"],"sourcesContent":["/**\n * Opt-in Resend transport for the teams invitations `sendInvitationEmail` seam.\n * Most adopters back the seam with Resend and hand-roll the same wrapper — and\n * the same mistake: `resend.emails.send()` returns `{ data, error }` and does NOT\n * throw on an API-level failure (unverified domain, rate limit, bad recipient),\n * so a `try/catch` alone records a failed send as a success. This helper sends\n * through the shared `renderInvitationEmail` template and returns a typed failure\n * on BOTH a thrown error AND a non-null `result.error`.\n *\n * `resend` is an OPTIONAL peer, imported only here: apps not on Resend keep\n * passing a raw seam, and the package core never pulls a mail dependency.\n */\n\nimport { Resend } from 'resend'\nimport { renderInvitationEmail } from './invitations'\nimport type { SendInvitationEmailSeam } from './invitations-api'\n\n/** Define options for sending a resend invitation including sender address and optional API key */\nexport interface ResendInvitationSenderOptions {\n /** RFC-5322 From header, e.g. `GTM Agent <noreply@gtm.tangle.tools>`. */\n from: string\n /** Resend API key. Defaults to `process.env.RESEND_API_KEY`. */\n apiKey?: string\n}\n\n/**\n * Build a `sendInvitationEmail` seam backed by Resend. Wire it into\n * `createInvitationsApi({ sendInvitationEmail: createResendInvitationSender({ from }) })`.\n * The client is built lazily on first send; with no key the seam fails typed\n * (the invitation is still created — emailStatus becomes 'failed').\n */\nexport function createResendInvitationSender(opts: ResendInvitationSenderOptions): SendInvitationEmailSeam {\n let client: Resend | null = null\n\n function getClient(): Resend | null {\n if (client) return client\n const key = opts.apiKey ?? (typeof process !== 'undefined' ? process.env.RESEND_API_KEY : undefined)\n if (!key) return null\n client = new Resend(key)\n return client\n }\n\n return async (input) => {\n const resend = getClient()\n if (!resend) return { succeeded: false, error: 'RESEND_API_KEY is not configured' }\n\n const msg = renderInvitationEmail(input, { fromAddress: opts.from })\n try {\n const result = await resend.emails.send({\n from: msg.from,\n to: input.to,\n subject: msg.subject,\n html: msg.html,\n text: msg.text,\n })\n // Resend reports API-level failures in `result.error` WITHOUT throwing — a\n // try/catch alone would mark a failed send as a success.\n if (result.error) return { succeeded: false, error: result.error.message }\n return { succeeded: true }\n } catch (err) {\n return { succeeded: false, error: err instanceof Error ? err.message : 'Invitation email failed to send' }\n }\n }\n}\n"],"mappings":";;;;;AAaA,SAAS,cAAc;AAkBhB,SAAS,6BAA6B,MAA8D;AACzG,MAAI,SAAwB;AAE5B,WAAS,YAA2B;AAClC,QAAI,OAAQ,QAAO;AACnB,UAAM,MAAM,KAAK,WAAW,OAAO,YAAY,cAAc,QAAQ,IAAI,iBAAiB;AAC1F,QAAI,CAAC,IAAK,QAAO;AACjB,aAAS,IAAI,OAAO,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,UAAU;AACtB,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ,QAAO,EAAE,WAAW,OAAO,OAAO,mCAAmC;AAElF,UAAM,MAAM,sBAAsB,OAAO,EAAE,aAAa,KAAK,KAAK,CAAC;AACnE,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,OAAO,KAAK;AAAA,QACtC,MAAM,IAAI;AAAA,QACV,IAAI,MAAM;AAAA,QACV,SAAS,IAAI;AAAA,QACb,MAAM,IAAI;AAAA,QACV,MAAM,IAAI;AAAA,MACZ,CAAC;AAGD,UAAI,OAAO,MAAO,QAAO,EAAE,WAAW,OAAO,OAAO,OAAO,MAAM,QAAQ;AACzE,aAAO,EAAE,WAAW,KAAK;AAAA,IAC3B,SAAS,KAAK;AACZ,aAAO,EAAE,WAAW,OAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,kCAAkC;AAAA,IAC3G;AAAA,EACF;AACF;","names":[]}
|