- Introduced the `@convex-dev/migrations` package to manage database migrations. - Updated organization and workspace creation logic to support anonymous organizations and workspaces. - Refactored session context to include new handling for anonymous mode. - Enhanced UI components to reflect changes in organization and workspace naming conventions. - Improved type definitions and API interactions for better clarity and functionality.
Unified · Split
| @@ -6,6 +6,7 @@ · 5 unmodified lines | |||
| 6 | "name": "proto-monorepo", | 6 | "name": "proto-monorepo", |
| 7 | "dependencies": { | 7 | "dependencies": { |
| 8 | "@apidevtools/swagger-parser": "^12.1.0", | 8 | "@apidevtools/swagger-parser": "^12.1.0", |
| 9 | "@convex-dev/migrations": "^0.3.1", | ||
| 9 | "@convex-dev/stripe": "^0.1.3", | 10 | "@convex-dev/stripe": "^0.1.3", |
| 10 | "@convex-dev/workos-authkit": "^0.1.6", | 11 | "@convex-dev/workos-authkit": "^0.1.6", |
| 11 | "@executor/contracts": "workspace:*", | 12 | "@executor/contracts": "workspace:*", |
| @@ -284,6 +285,8 @@ · 272 unmodified lines | |||
| 284 | 285 | ||
| 285 | "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], | 286 | "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], |
| 286 | 287 | ||
| 288 | "@convex-dev/migrations": ["@convex-dev/migrations@0.3.1", "", { "peerDependencies": { "convex": "^1.24.8" } }, "sha512-dGiN82vWMN415/AGMcVooDOJk8iTousZahBBs27uA03pL74Uo3ksucUW3CiCfPx75rv+x9UnenEakimQYYNIrA=="], | ||
| 289 | |||
| 287 | "@convex-dev/stripe": ["@convex-dev/stripe@0.1.3", "", { "dependencies": { "stripe": "^20.0.0" }, "peerDependencies": { "convex": "^1.29.3", "react": "^18.3.1 || ^19.0.0" } }, "sha512-yL0ygrWrjto9f7rprCb4KYp+p3Oka7KG5e3HBoJ9qSzbncpavZI+JTkGZnLCYZjPOo2ByCrBJvtGAxfP169Nzw=="], | 290 | "@convex-dev/stripe": ["@convex-dev/stripe@0.1.3", "", { "dependencies": { "stripe": "^20.0.0" }, "peerDependencies": { "convex": "^1.29.3", "react": "^18.3.1 || ^19.0.0" } }, "sha512-yL0ygrWrjto9f7rprCb4KYp+p3Oka7KG5e3HBoJ9qSzbncpavZI+JTkGZnLCYZjPOo2ByCrBJvtGAxfP169Nzw=="], |
| 288 | 291 | ||
| 289 | "@convex-dev/workos": ["@convex-dev/workos@0.0.1", "", { "dependencies": { "@workos-inc/authkit-react": "^0.11.0", "tsdown": "^0.12.7", "vitest": "^3.1.4" }, "peerDependencies": { "convex": "^1.25.4", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" } }, "sha512-8gZOgmcTitcKXwagdU69XC4Va6wMPFIhSqSOEaXmFXMEPtkMgxPW1dhJzrmm9UQ4iRgZsckjd2O5aQjUH7kHGQ=="], | 292 | "@convex-dev/workos": ["@convex-dev/workos@0.0.1", "", { "dependencies": { "@workos-inc/authkit-react": "^0.11.0", "tsdown": "^0.12.7", "vitest": "^3.1.4" }, "peerDependencies": { "convex": "^1.25.4", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" } }, "sha512-8gZOgmcTitcKXwagdU69XC4Va6wMPFIhSqSOEaXmFXMEPtkMgxPW1dhJzrmm9UQ4iRgZsckjd2O5aQjUH7kHGQ=="], |
| @@ -104,12 +104,13 @@ function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { · 103 unmodified lines | |||
| 104 | const activeWorkspace = context | 104 | const activeWorkspace = context |
| 105 | ? workspaces.find((workspace) => workspace.id === context.workspaceId) | 105 | ? workspaces.find((workspace) => workspace.id === context.workspaceId) |
| 106 | : null; | 106 | : null; |
| 107 | const supportsOrganizationManagement = mode !== "guest"; | ||
| 107 | const activeOrganizationLabel = activeWorkspace?.organizationName ?? "Organization"; | 108 | const activeOrganizationLabel = activeWorkspace?.organizationName ?? "Organization"; |
| 108 | const activeWorkspaceLabel = activeWorkspace?.name ?? (mode === "guest" ? "Guest Workspace" : "Select workspace"); | 109 | const activeWorkspaceLabel = activeWorkspace?.name ?? (mode === "guest" ? "Anonymous Workspace" : "Select workspace"); |
| 109 | const activeWorkspaceInitial = (activeWorkspaceLabel[0] ?? "W").toUpperCase(); | 110 | const activeWorkspaceInitial = (activeWorkspaceLabel[0] ?? "W").toUpperCase(); |
| 110 | 111 | ||
| 111 | const workspaceGroups = useMemo(() => { | 112 | const workspaceGroups = useMemo(() => { |
| 112 | if (mode !== "workos") { | 113 | if (!supportsOrganizationManagement) { |
| 113 | return []; | 114 | return []; |
| 114 | } | 115 | } |
| 115 | 116 | ||
| @@ -129,7 +130,7 @@ function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { · 13 unmodified lines | |||
| 129 | groups.push({ | 130 | groups.push({ |
| 130 | key, | 131 | key, |
| 131 | organizationId: workspace.organizationId, | 132 | organizationId: workspace.organizationId, |
| 132 | organizationName: workspace.organizationName ?? "Organization", | 133 | organizationName: workspace.organizationName, |
| 133 | workspaces: [workspace], | 134 | workspaces: [workspace], |
| 134 | }); | 135 | }); |
| 135 | continue; | 136 | continue; |
| @@ -139,7 +140,7 @@ function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { · 3 unmodified lines | |||
| 139 | } | 140 | } |
| 140 | 141 | ||
| 141 | return groups; | 142 | return groups; |
| 142 | }, [mode, workspaces]); | 143 | }, [supportsOrganizationManagement, workspaces]); |
| 143 | 144 | ||
| 144 | const openCreateWorkspaceForOrganization = ( | 145 | const openCreateWorkspaceForOrganization = ( |
| 145 | organizationId: (typeof workspaces)[number]["organizationId"], | 146 | organizationId: (typeof workspaces)[number]["organizationId"], |
| @@ -225,7 +226,7 @@ function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { · 79 unmodified lines | |||
| 225 | </span> | 226 | </span> |
| 226 | )} | 227 | )} |
| 227 | <span className="min-w-0"> | 228 | <span className="min-w-0"> |
| 228 | {mode === "workos" ? ( | 229 | {supportsOrganizationManagement ? ( |
| 229 | <> | 230 | <> |
| 230 | <span className="truncate block text-[10px] text-muted-foreground">{activeOrganizationLabel}</span> | 231 | <span className="truncate block text-[10px] text-muted-foreground">{activeOrganizationLabel}</span> |
| 231 | <span className="truncate block">{activeWorkspaceLabel}</span> | 232 | <span className="truncate block">{activeWorkspaceLabel}</span> |
| @@ -239,7 +240,7 @@ function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { · 7 unmodified lines | |||
| 239 | </Button> | 240 | </Button> |
| 240 | </DropdownMenuTrigger> | 241 | </DropdownMenuTrigger> |
| 241 | <DropdownMenuContent align="end" className="w-72"> | 242 | <DropdownMenuContent align="end" className="w-72"> |
| 242 | {mode === "workos" | 243 | {supportsOrganizationManagement |
| 243 | ? ( | 244 | ? ( |
| 244 | <> | 245 | <> |
| 245 | {workspaceGroups.map((group, groupIndex) => { | 246 | {workspaceGroups.map((group, groupIndex) => { |
| @@ -319,7 +320,7 @@ function WorkspaceSelector({ inHeader = false }: { inHeader?: boolean }) { · 73 unmodified lines | |||
| 319 | Guest workspace | 320 | Guest workspace |
| 320 | </DropdownMenuItem> | 321 | </DropdownMenuItem> |
| 321 | )} | 322 | )} |
| 322 | {mode === "workos" ? ( | 323 | {supportsOrganizationManagement ? ( |
| 323 | <> | 324 | <> |
| 324 | <DropdownMenuSeparator /> | 325 | <DropdownMenuSeparator /> |
| 325 | <DropdownMenuItem onSelect={openCreateOrganization} className="text-xs"> | 326 | <DropdownMenuItem onSelect={openCreateOrganization} className="text-xs"> |
| @@ -668,8 +669,8 @@ function NoOrganizationModal({ enabled }: { enabled: boolean }) { · 342 unmodified lines | |||
| 668 | organizationsLoading, | 669 | organizationsLoading, |
| 669 | context, | 670 | context, |
| 670 | isSignedInToWorkos, | 671 | isSignedInToWorkos, |
| 671 | createAnonymousWorkspace, | 672 | createAnonymousOrganization, |
| 672 | creatingAnonymousWorkspace, | 673 | creatingAnonymousOrganization, |
| 673 | } = useSession(); | 674 | } = useSession(); |
| 674 | const [error, setError] = useState<string | null>(null); | 675 | const [error, setError] = useState<string | null>(null); |
| 675 | 676 | ||
| @@ -680,12 +681,12 @@ function NoOrganizationModal({ enabled }: { enabled: boolean }) { · 4 unmodified lines | |||
| 680 | && !isSignedInToWorkos | 681 | && !isSignedInToWorkos |
| 681 | && organizations.length === 0; | 682 | && organizations.length === 0; |
| 682 | 683 | ||
| 683 | const handleCreateAnonymousWorkspace = async () => { | 684 | const handleCreateAnonymousOrganization = async () => { |
| 684 | setError(null); | 685 | setError(null); |
| 685 | try { | 686 | try { |
| 686 | await createAnonymousWorkspace(); | 687 | await createAnonymousOrganization(); |
| 687 | } catch (cause) { | 688 | } catch (cause) { |
| 688 | const message = cause instanceof Error ? cause.message : "Failed to create anonymous workspace"; | 689 | const message = cause instanceof Error ? cause.message : "Failed to create anonymous organization"; |
| 689 | setError(message); | 690 | setError(message); |
| 690 | } | 691 | } |
| 691 | }; | 692 | }; |
| @@ -700,12 +701,12 @@ function NoOrganizationModal({ enabled }: { enabled: boolean }) { · 8 unmodified lines | |||
| 700 | <DialogHeader> | 701 | <DialogHeader> |
| 701 | <DialogTitle>Choose how to continue</DialogTitle> | 702 | <DialogTitle>Choose how to continue</DialogTitle> |
| 702 | <DialogDescription> | 703 | <DialogDescription> |
| 703 | Sign in to access your organizations, or create an anonymous workspace to continue as a guest. | 704 | Sign in to access your organizations, or create an anonymous organization with a default workspace. |
| 704 | </DialogDescription> | 705 | </DialogDescription> |
| 705 | </DialogHeader> | 706 | </DialogHeader> |
| 706 | <div className="space-y-2"> | 707 | <div className="space-y-2"> |
| 707 | {workosEnabled ? ( | 708 | {workosEnabled ? ( |
| 708 | <Button asChild className="w-full" disabled={creatingAnonymousWorkspace}> | 709 | <Button asChild className="w-full" disabled={creatingAnonymousOrganization}> |
| 709 | <Link to="/sign-in" reloadDocument className="gap-2"> | 710 | <Link to="/sign-in" reloadDocument className="gap-2"> |
| 710 | <LogIn className="h-4 w-4" /> | 711 | <LogIn className="h-4 w-4" /> |
| 711 | Sign in | 712 | Sign in |
| @@ -715,13 +716,13 @@ function NoOrganizationModal({ enabled }: { enabled: boolean }) { · 3 unmodified lines | |||
| 715 | <Button | 716 | <Button |
| 716 | variant="outline" | 717 | variant="outline" |
| 717 | className="w-full" | 718 | className="w-full" |
| 718 | onClick={handleCreateAnonymousWorkspace} | 719 | onClick={handleCreateAnonymousOrganization} |
| 719 | disabled={creatingAnonymousWorkspace || !anonymousDemoEnabled} | 720 | disabled={creatingAnonymousOrganization || !anonymousDemoEnabled} |
| 720 | > | 721 | > |
| 721 | {creatingAnonymousWorkspace ? "Creating anonymous workspace..." : "Create anonymous workspace"} | 722 | {creatingAnonymousOrganization ? "Creating anonymous organization..." : "Create anonymous organization"} |
| 722 | </Button> | 723 | </Button> |
| 723 | {!anonymousDemoEnabled ? ( | 724 | {!anonymousDemoEnabled ? ( |
| 724 | <p className="text-xs text-muted-foreground">Anonymous workspace creation is disabled.</p> | 725 | <p className="text-xs text-muted-foreground">Anonymous organization creation is disabled.</p> |
| 725 | ) : null} | 726 | ) : null} |
| 726 | {error ? <p className="text-xs text-destructive">{error}</p> : null} | 727 | {error ? <p className="text-xs text-destructive">{error}</p> : null} |
| 727 | </div> | 728 | </div> |
| @@ -29,7 +29,7 @@ interface SessionState { · 28 unmodified lines | |||
| 29 | workspaceRestrictions: boolean; | 29 | workspaceRestrictions: boolean; |
| 30 | }; | 30 | }; |
| 31 | } | null; | 31 | } | null; |
| 32 | mode: "guest" | "workos"; | 32 | mode: "guest" | "workos" | "anonymous"; |
| 33 | organizations: Array<{ | 33 | organizations: Array<{ |
| 34 | id: Id<"organizations">; | 34 | id: Id<"organizations">; |
| 35 | name: string; | 35 | name: string; |
| @@ -43,8 +43,8 @@ interface SessionState { · 7 unmodified lines | |||
| 43 | docId: Id<"workspaces"> | null; | 43 | docId: Id<"workspaces"> | null; |
| 44 | name: string; | 44 | name: string; |
| 45 | organizationId: Id<"organizations"> | null; | 45 | organizationId: Id<"organizations"> | null; |
| 46 | organizationName: string | null; | 46 | organizationName: string; |
| 47 | organizationSlug: string | null; | 47 | organizationSlug: string; |
| 48 | iconUrl?: string | null; | 48 | iconUrl?: string | null; |
| 49 | }>; | 49 | }>; |
| 50 | switchWorkspace: (workspaceId: Id<"workspaces">) => void; | 50 | switchWorkspace: (workspaceId: Id<"workspaces">) => void; |
| @@ -54,8 +54,8 @@ interface SessionState { · 3 unmodified lines | |||
| 54 | iconFile?: File | null, | 54 | iconFile?: File | null, |
| 55 | organizationId?: Id<"organizations">, | 55 | organizationId?: Id<"organizations">, |
| 56 | ) => Promise<void>; | 56 | ) => Promise<void>; |
| 57 | creatingAnonymousWorkspace: boolean; | 57 | creatingAnonymousOrganization: boolean; |
| 58 | createAnonymousWorkspace: () => Promise<void>; | 58 | createAnonymousOrganization: () => Promise<void>; |
| 59 | isSignedInToWorkos: boolean; | 59 | isSignedInToWorkos: boolean; |
| 60 | workosProfile: { | 60 | workosProfile: { |
| 61 | name: string; | 61 | name: string; |
| @@ -76,9 +76,9 @@ interface WorkosAccount { · 14 unmodified lines | |||
| 76 | 76 | ||
| 77 | interface WorkspaceListItem { | 77 | interface WorkspaceListItem { |
| 78 | id: Id<"workspaces">; | 78 | id: Id<"workspaces">; |
| 79 | organizationId: Id<"organizations"> | null; | 79 | organizationId: Id<"organizations">; |
| 80 | organizationName: string | null; | 80 | organizationName: string; |
| 81 | organizationSlug: string | null; | 81 | organizationSlug: string; |
| 82 | name: string; | 82 | name: string; |
| 83 | slug: string; | 83 | slug: string; |
| 84 | iconUrl?: string | null; | 84 | iconUrl?: string | null; |
| @@ -97,8 +97,8 @@ const SessionContext = createContext<SessionState>({ · 12 unmodified lines | |||
| 97 | switchWorkspace: () => {}, | 97 | switchWorkspace: () => {}, |
| 98 | creatingWorkspace: false, | 98 | creatingWorkspace: false, |
| 99 | createWorkspace: async () => {}, | 99 | createWorkspace: async () => {}, |
| 100 | creatingAnonymousWorkspace: false, | 100 | creatingAnonymousOrganization: false, |
| 101 | createAnonymousWorkspace: async () => {}, | 101 | createAnonymousOrganization: async () => {}, |
| 102 | isSignedInToWorkos: false, | 102 | isSignedInToWorkos: false, |
| 103 | workosProfile: null, | 103 | workosProfile: null, |
| 104 | resetWorkspace: async () => {}, | 104 | resetWorkspace: async () => {}, |
| @@ -147,7 +147,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 42 unmodified lines | |||
| 147 | const createWorkspaceMutation = useMutation(convexApi.workspaces.create); | 147 | const createWorkspaceMutation = useMutation(convexApi.workspaces.create); |
| 148 | const generateWorkspaceIconUploadUrl = useMutation(convexApi.workspaces.generateWorkspaceIconUploadUrl); | 148 | const generateWorkspaceIconUploadUrl = useMutation(convexApi.workspaces.generateWorkspaceIconUploadUrl); |
| 149 | const [creatingWorkspace, setCreatingWorkspace] = useState(false); | 149 | const [creatingWorkspace, setCreatingWorkspace] = useState(false); |
| 150 | const [creatingAnonymousWorkspace, setCreatingAnonymousWorkspace] = useState(false); | 150 | const [creatingAnonymousOrganization, setCreatingAnonymousOrganization] = useState(false); |
| 151 | const [manualGuestContext, setManualGuestContext] = useState<AnonymousContext | null>(null); | 151 | const [manualGuestContext, setManualGuestContext] = useState<AnonymousContext | null>(null); |
| 152 | 152 | ||
| 153 | const bootstrapSessionQuery = useTanstackQuery({ | 153 | const bootstrapSessionQuery = useTanstackQuery({ |
| @@ -173,15 +173,15 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 19 unmodified lines | |||
| 173 | 173 | ||
| 174 | const account = useConvexQuery( | 174 | const account = useConvexQuery( |
| 175 | convexApi.app.getCurrentAccount, | 175 | convexApi.app.getCurrentAccount, |
| 176 | workosEnabled ? { sessionId: storedSessionId ?? undefined } : "skip", | 176 | { sessionId: storedSessionId ?? undefined }, |
| 177 | ) as WorkosAccount | null | undefined; | 177 | ) as WorkosAccount | null | undefined; |
| 178 | const workspaces = useConvexQuery( | 178 | const workspaces = useConvexQuery( |
| 179 | convexApi.workspaces.list, | 179 | convexApi.workspaces.list, |
| 180 | workosEnabled ? { sessionId: storedSessionId ?? undefined } : "skip", | 180 | { sessionId: storedSessionId ?? undefined }, |
| 181 | ) as WorkspaceListItem[] | undefined; | 181 | ) as WorkspaceListItem[] | undefined; |
| 182 | const organizations = useConvexQuery( | 182 | const organizations = useConvexQuery( |
| 183 | convexApi.organizations.listMine, | 183 | convexApi.organizations.listMine, |
| 184 | workosEnabled ? { sessionId: storedSessionId ?? undefined } : "skip", | 184 | { sessionId: storedSessionId ?? undefined }, |
| 185 | ); | 185 | ); |
| 186 | 186 | ||
| 187 | const resolvedActiveWorkspaceId = useMemo(() => { | 187 | const resolvedActiveWorkspaceId = useMemo(() => { |
| @@ -193,7 +193,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 5 unmodified lines | |||
| 193 | return activeWorkspaceId; | 193 | return activeWorkspaceId; |
| 194 | } | 194 | } |
| 195 | 195 | ||
| 196 | const accountId = account?.provider === "workos" ? account._id : null; | 196 | const accountId = account?._id ?? null; |
| 197 | const accountStoredWorkspace = accountId ? readWorkspaceByAccount()[accountId] : null; | 197 | const accountStoredWorkspace = accountId ? readWorkspaceByAccount()[accountId] : null; |
| 198 | if (accountStoredWorkspace && workspaces.some((workspace) => workspace.id === accountStoredWorkspace)) { | 198 | if (accountStoredWorkspace && workspaces.some((workspace) => workspace.id === accountStoredWorkspace)) { |
| 199 | return accountStoredWorkspace; | 199 | return accountStoredWorkspace; |
| @@ -219,13 +219,13 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 19 unmodified lines | |||
| 219 | setRuntimeError(null); | 219 | setRuntimeError(null); |
| 220 | }, []); | 220 | }, []); |
| 221 | 221 | ||
| 222 | const createAnonymousWorkspace = useCallback(async () => { | 222 | const createAnonymousOrganization = useCallback(async () => { |
| 223 | if (!anonymousDemoEnabled) { | 223 | if (!anonymousDemoEnabled) { |
| 224 | throw new Error("Anonymous workspace creation is disabled"); | 224 | throw new Error("Anonymous organization creation is disabled"); |
| 225 | } | 225 | } |
| 226 | 226 | ||
| 227 | setRuntimeError(null); | 227 | setRuntimeError(null); |
| 228 | setCreatingAnonymousWorkspace(true); | 228 | setCreatingAnonymousOrganization(true); |
| 229 | try { | 229 | try { |
| 230 | const context = await bootstrapAnonymousSession({}); | 230 | const context = await bootstrapAnonymousSession({}); |
| 231 | localStorage.setItem(SESSION_KEY, context.sessionId); | 231 | localStorage.setItem(SESSION_KEY, context.sessionId); |
| @@ -234,11 +234,11 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 2 unmodified lines | |||
| 234 | setActiveWorkspaceId(context.workspaceId as Id<"workspaces">); | 234 | setActiveWorkspaceId(context.workspaceId as Id<"workspaces">); |
| 235 | setManualGuestContext(context); | 235 | setManualGuestContext(context); |
| 236 | } catch (cause) { | 236 | } catch (cause) { |
| 237 | const message = cause instanceof Error ? cause.message : "Failed to create anonymous workspace"; | 237 | const message = cause instanceof Error ? cause.message : "Failed to create anonymous organization"; |
| 238 | setRuntimeError(message); | 238 | setRuntimeError(message); |
| 239 | throw cause; | 239 | throw cause; |
| 240 | } finally { | 240 | } finally { |
| 241 | setCreatingAnonymousWorkspace(false); | 241 | setCreatingAnonymousOrganization(false); |
| 242 | } | 242 | } |
| 243 | }, [bootstrapAnonymousSession]); | 243 | }, [bootstrapAnonymousSession]); |
| 244 | 244 | ||
| @@ -246,7 +246,7 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 1 unmodified line | |||
| 246 | setActiveWorkspaceId(workspaceId); | 246 | setActiveWorkspaceId(workspaceId); |
| 247 | localStorage.setItem(ACTIVE_WORKSPACE_KEY, workspaceId); | 247 | localStorage.setItem(ACTIVE_WORKSPACE_KEY, workspaceId); |
| 248 | 248 | ||
| 249 | if (account?.provider === "workos") { | 249 | if (account) { |
| 250 | const accountId = account._id; | 250 | const accountId = account._id; |
| 251 | const byAccount = readWorkspaceByAccount(); | 251 | const byAccount = readWorkspaceByAccount(); |
| 252 | writeWorkspaceByAccount({ | 252 | writeWorkspaceByAccount({ |
| @@ -314,8 +314,8 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 61 unmodified lines | |||
| 314 | switchWorkspace, | 314 | switchWorkspace, |
| 315 | ]); | 315 | ]); |
| 316 | 316 | ||
| 317 | const workosContext = useMemo<AnonymousContext | null>(() => { | 317 | const accountWorkspaceContext = useMemo<AnonymousContext | null>(() => { |
| 318 | if (!workosEnabled || !account || account.provider !== "workos" || !workspaces || workspaces.length === 0) { | 318 | if (!account || !workspaces || workspaces.length === 0) { |
| 319 | return null; | 319 | return null; |
| 320 | } | 320 | } |
| 321 | 321 | ||
| @@ -327,17 +327,26 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 5 unmodified lines | |||
| 327 | return null; | 327 | return null; |
| 328 | } | 328 | } |
| 329 | 329 | ||
| 330 | const sessionId = account.provider === "workos" | ||
| 331 | ? `workos_${account._id}` | ||
| 332 | : (storedSessionId ?? guestContext?.sessionId ?? null); | ||
| 333 | if (!sessionId) { | ||
| 334 | return null; | ||
| 335 | } | ||
| 336 | |||
| 337 | const actorId = account.provider === "workos" ? account._id : account.providerAccountId; | ||
| 338 | |||
| 330 | return { | 339 | return { |
| 331 | sessionId: `workos_${account._id}`, | 340 | sessionId, |
| 332 | workspaceId: activeWorkspace.id, | 341 | workspaceId: activeWorkspace.id, |
| 333 | actorId: account._id, | 342 | actorId, |
| 334 | clientId: "web", | 343 | clientId: "web", |
| 335 | accountId: account._id, | 344 | accountId: account._id, |
| 336 | userId: account._id, | 345 | userId: account._id, |
| 337 | createdAt: Date.now(), | 346 | createdAt: Date.now(), |
| 338 | lastSeenAt: Date.now(), | 347 | lastSeenAt: Date.now(), |
| 339 | }; | 348 | }; |
| 340 | }, [account, resolvedActiveWorkspaceId, workspaces]); | 349 | }, [account, guestContext?.sessionId, resolvedActiveWorkspaceId, storedSessionId, workspaces]); |
| 341 | 350 | ||
| 342 | // When WorkOS is enabled, don't fall back to guest context while WorkOS | 351 | // When WorkOS is enabled, don't fall back to guest context while WorkOS |
| 343 | // auth/account bootstrapping is still in flight. Otherwise workspace-bound | 352 | // auth/account bootstrapping is still in flight. Otherwise workspace-bound |
| @@ -348,9 +357,11 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 4 unmodified lines | |||
| 348 | || account === undefined | 357 | || account === undefined |
| 349 | || (account?.provider === "workos" && bootstrapWorkosAccountQuery.isFetching) | 358 | || (account?.provider === "workos" && bootstrapWorkosAccountQuery.isFetching) |
| 350 | ); | 359 | ); |
| 351 | const mode: "guest" | "workos" = workosContext ? "workos" : "guest"; | 360 | const mode: "guest" | "workos" | "anonymous" = accountWorkspaceContext |
| 361 | ? (account?.provider === "workos" ? "workos" : "anonymous") | ||
| 362 | : "guest"; | ||
| 352 | const shouldBlockGuestFallback = workosEnabled && account?.provider === "workos"; | 363 | const shouldBlockGuestFallback = workosEnabled && account?.provider === "workos"; |
| 353 | const context = workosContext ?? ((workosStillLoading || shouldBlockGuestFallback) ? null : guestContext); | 364 | const context = accountWorkspaceContext ?? ((workosStillLoading || shouldBlockGuestFallback) ? null : guestContext); |
| 354 | 365 | ||
| 355 | const bootstrapSessionError = | 366 | const bootstrapSessionError = |
| 356 | storedSessionId && bootstrapSessionQuery.error instanceof Error | 367 | storedSessionId && bootstrapSessionQuery.error instanceof Error |
| @@ -367,22 +378,24 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 10 unmodified lines | |||
| 367 | const error = runtimeError ?? bootstrapSessionError ?? bootstrapWorkosError; | 378 | const error = runtimeError ?? bootstrapSessionError ?? bootstrapWorkosError; |
| 368 | 379 | ||
| 369 | const effectiveLoading = !context && !error && ( | 380 | const effectiveLoading = !context && !error && ( |
| 370 | creatingAnonymousWorkspace | 381 | creatingAnonymousOrganization |
| 371 | || (storedSessionId !== null && bootstrapSessionQuery.isLoading) | 382 | || (storedSessionId !== null && bootstrapSessionQuery.isLoading) |
| 372 | || workosStillLoading | 383 | || workosStillLoading |
| 373 | || bootstrapWorkosAccountQuery.isFetching | 384 | || bootstrapWorkosAccountQuery.isFetching |
| 374 | ); | 385 | ); |
| 375 | const workspaceOptions = useMemo(() => { | 386 | const workspaceOptions = useMemo(() => { |
| 376 | if (mode === "workos" && workspaces) { | 387 | if (mode !== "guest" && workspaces) { |
| 377 | return workspaces.map((workspace): SessionState["workspaces"][number] => ({ | 388 | return workspaces.map((workspace): SessionState["workspaces"][number] => { |
| 378 | id: workspace.id, | 389 | return { |
| 379 | docId: workspace.id, | 390 | id: workspace.id, |
| 380 | name: workspace.name, | 391 | docId: workspace.id, |
| 381 | organizationId: workspace.organizationId ?? null, | 392 | name: workspace.name, |
| 382 | organizationName: workspace.organizationName, | 393 | organizationId: workspace.organizationId, |
| 383 | organizationSlug: workspace.organizationSlug, | 394 | organizationName: workspace.organizationName, |
| 384 | iconUrl: workspace.iconUrl, | 395 | organizationSlug: workspace.organizationSlug, |
| 385 | })); | 396 | iconUrl: workspace.iconUrl, |
| 397 | }; | ||
| 398 | }); | ||
| 386 | } | 399 | } |
| 387 | 400 | ||
| 388 | if (guestContext) { | 401 | if (guestContext) { |
| @@ -390,10 +403,10 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 1 unmodified line | |||
| 390 | { | 403 | { |
| 391 | id: guestContext.workspaceId as Id<"workspaces">, | 404 | id: guestContext.workspaceId as Id<"workspaces">, |
| 392 | docId: null, | 405 | docId: null, |
| 393 | name: "Guest Workspace", | 406 | name: "Anonymous Workspace", |
| 394 | organizationId: null, | 407 | organizationId: null, |
| 395 | organizationName: null, | 408 | organizationName: "Anonymous Organization", |
| 396 | organizationSlug: null, | 409 | organizationSlug: "anonymous-organization", |
| 397 | }, | 410 | }, |
| 398 | ]; | 411 | ]; |
| 399 | } | 412 | } |
| @@ -410,13 +423,13 @@ export function SessionProvider({ children }: { children: ReactNode }) { · 10 unmodified lines | |||
| 410 | clientConfig: clientConfig ?? null, | 423 | clientConfig: clientConfig ?? null, |
| 411 | mode, | 424 | mode, |
| 412 | organizations: organizations ?? [], | 425 | organizations: organizations ?? [], |
| 413 | organizationsLoading: workosEnabled ? organizations === undefined : false, | 426 | organizationsLoading: organizations === undefined, |
| 414 | workspaces: workspaceOptions, | 427 | workspaces: workspaceOptions, |
| 415 | switchWorkspace, | 428 | switchWorkspace, |
| 416 | creatingWorkspace, | 429 | creatingWorkspace, |
| 417 | createWorkspace, | 430 | createWorkspace, |
| 418 | creatingAnonymousWorkspace, | 431 | creatingAnonymousOrganization, |
| 419 | createAnonymousWorkspace, | 432 | createAnonymousOrganization, |
| 420 | isSignedInToWorkos: Boolean(account && account.provider === "workos"), | 433 | isSignedInToWorkos: Boolean(account && account.provider === "workos"), |
| 421 | workosProfile: | 434 | workosProfile: |
| 422 | account && account.provider === "workos" | 435 | account && account.provider === "workos" |
| @@ -813,6 +813,80 @@ export declare const internal: { · 812 unmodified lines | |||
| 813 | any | 813 | any |
| 814 | >; | 814 | >; |
| 815 | }; | 815 | }; |
| 816 | migrations: { | ||
| 817 | deleteAnonymousSessionsMissingAccountId: FunctionReference< | ||
| 818 | "mutation", | ||
| 819 | "internal", | ||
| 820 | { | ||
| 821 | batchSize?: number; | ||
| 822 | cursor?: string | null; | ||
| 823 | dryRun?: boolean; | ||
| 824 | fn?: string; | ||
| 825 | next?: Array<string>; | ||
| 826 | }, | ||
| 827 | any | ||
| 828 | >; | ||
| 829 | deleteInvitesMissingProvider: FunctionReference< | ||
| 830 | "mutation", | ||
| 831 | "internal", | ||
| 832 | { | ||
| 833 | batchSize?: number; | ||
| 834 | cursor?: string | null; | ||
| 835 | dryRun?: boolean; | ||
| 836 | fn?: string; | ||
| 837 | next?: Array<string>; | ||
| 838 | }, | ||
| 839 | any | ||
| 840 | >; | ||
| 841 | deleteSourceCredentialsMissingProvider: FunctionReference< | ||
| 842 | "mutation", | ||
| 843 | "internal", | ||
| 844 | { | ||
| 845 | batchSize?: number; | ||
| 846 | cursor?: string | null; | ||
| 847 | dryRun?: boolean; | ||
| 848 | fn?: string; | ||
| 849 | next?: Array<string>; | ||
| 850 | }, | ||
| 851 | any | ||
| 852 | >; | ||
| 853 | renameLegacyAnonymousOrganizations: FunctionReference< | ||
| 854 | "mutation", | ||
| 855 | "internal", | ||
| 856 | { | ||
| 857 | batchSize?: number; | ||
| 858 | cursor?: string | null; | ||
| 859 | dryRun?: boolean; | ||
| 860 | fn?: string; | ||
| 861 | next?: Array<string>; | ||
| 862 | }, | ||
| 863 | any | ||
| 864 | >; | ||
| 865 | renameLegacyAnonymousWorkspaces: FunctionReference< | ||
| 866 | "mutation", | ||
| 867 | "internal", | ||
| 868 | { | ||
| 869 | batchSize?: number; | ||
| 870 | cursor?: string | null; | ||
| 871 | dryRun?: boolean; | ||
| 872 | fn?: string; | ||
| 873 | next?: Array<string>; | ||
| 874 | }, | ||
| 875 | any | ||
| 876 | >; | ||
| 877 | run: FunctionReference< | ||
| 878 | "mutation", | ||
| 879 | "internal", | ||
| 880 | { | ||
| 881 | batchSize?: number; | ||
| 882 | cursor?: string | null; | ||
| 883 | dryRun?: boolean; | ||
| 884 | fn?: string; | ||
| 885 | next?: Array<string>; | ||
| 886 | }, | ||
| 887 | any | ||
| 888 | >; | ||
| 889 | }; | ||
| 816 | openApiSpecCache: { | 890 | openApiSpecCache: { |
| 817 | getEntry: FunctionReference< | 891 | getEntry: FunctionReference< |
| 818 | "query", | 892 | "query", |
| @@ -1270,4 +1344,90 @@ export declare const components: { · 451 unmodified lines | |||
| 1270 | >; | 1344 | >; |
| 1271 | }; | 1345 | }; |
| 1272 | }; | 1346 | }; |
| 1347 | migrations: { | ||
| 1348 | lib: { | ||
| 1349 | cancel: FunctionReference< | ||
| 1350 | "mutation", | ||
| 1351 | "internal", | ||
| 1352 | { name: string }, | ||
| 1353 | { | ||
| 1354 | batchSize?: number; | ||
| 1355 | cursor?: string | null; | ||
| 1356 | error?: string; | ||
| 1357 | isDone: boolean; | ||
| 1358 | latestEnd?: number; | ||
| 1359 | latestStart: number; | ||
| 1360 | name: string; | ||
| 1361 | next?: Array<string>; | ||
| 1362 | processed: number; | ||
| 1363 | state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; | ||
| 1364 | } | ||
| 1365 | >; | ||
| 1366 | cancelAll: FunctionReference< | ||
| 1367 | "mutation", | ||
| 1368 | "internal", | ||
| 1369 | { sinceTs?: number }, | ||
| 1370 | Array<{ | ||
| 1371 | batchSize?: number; | ||
| 1372 | cursor?: string | null; | ||
| 1373 | error?: string; | ||
| 1374 | isDone: boolean; | ||
| 1375 | latestEnd?: number; | ||
| 1376 | latestStart: number; | ||
| 1377 | name: string; | ||
| 1378 | next?: Array<string>; | ||
| 1379 | processed: number; | ||
| 1380 | state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; | ||
| 1381 | }> | ||
| 1382 | >; | ||
| 1383 | clearAll: FunctionReference< | ||
| 1384 | "mutation", | ||
| 1385 | "internal", | ||
| 1386 | { before?: number }, | ||
| 1387 | null | ||
| 1388 | >; | ||
| 1389 | getStatus: FunctionReference< | ||
| 1390 | "query", | ||
| 1391 | "internal", | ||
| 1392 | { limit?: number; names?: Array<string> }, | ||
| 1393 | Array<{ | ||
| 1394 | batchSize?: number; | ||
| 1395 | cursor?: string | null; | ||
| 1396 | error?: string; | ||
| 1397 | isDone: boolean; | ||
| 1398 | latestEnd?: number; | ||
| 1399 | latestStart: number; | ||
| 1400 | name: string; | ||
| 1401 | next?: Array<string>; | ||
| 1402 | processed: number; | ||
| 1403 | state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; | ||
| 1404 | }> | ||
| 1405 | >; | ||
| 1406 | migrate: FunctionReference< | ||
| 1407 | "mutation", | ||
| 1408 | "internal", | ||
| 1409 | { | ||
| 1410 | batchSize?: number; | ||
| 1411 | cursor?: string | null; | ||
| 1412 | dryRun: boolean; | ||
| 1413 | fnHandle: string; | ||
| 1414 | name: string; | ||
| 1415 | next?: Array<{ fnHandle: string; name: string }>; | ||
| 1416 | oneBatchOnly?: boolean; | ||
| 1417 | }, | ||
| 1418 | { | ||
| 1419 | batchSize?: number; | ||
| 1420 | cursor?: string | null; | ||
| 1421 | error?: string; | ||
| 1422 | isDone: boolean; | ||
| 1423 | latestEnd?: number; | ||
| 1424 | latestStart: number; | ||
| 1425 | name: string; | ||
| 1426 | next?: Array<string>; | ||
| 1427 | processed: number; | ||
| 1428 | state: "inProgress" | "success" | "failed" | "canceled" | "unknown"; | ||
| 1429 | } | ||
| 1430 | >; | ||
| 1431 | }; | ||
| 1432 | }; | ||
| 1273 | }; | 1433 | }; |
| @@ -1,3 +1,4 @@ | |||
| 1 | import migrations from "@convex-dev/migrations/convex.config.js"; | ||
| 1 | import stripe from "@convex-dev/stripe/convex.config.js"; | 2 | import stripe from "@convex-dev/stripe/convex.config.js"; |
| 2 | import workOSAuthKit from "@convex-dev/workos-authkit/convex.config"; | 3 | import workOSAuthKit from "@convex-dev/workos-authkit/convex.config"; |
| 3 | import { defineApp } from "convex/server"; | 4 | import { defineApp } from "convex/server"; |
| @@ -6,5 +7,6 @@ const app = defineApp(); · 2 unmodified lines | |||
| 6 | 7 | ||
| 7 | app.use(workOSAuthKit); | 8 | app.use(workOSAuthKit); |
| 8 | app.use(stripe); | 9 | app.use(stripe); |
| 10 | app.use(migrations); | ||
| 9 | 11 | ||
| 10 | export default app; | 12 | export default app; |
| @@ -195,6 +195,8 @@ async function ensureAnonymousIdentity( · 194 unmodified lines | |||
| 195 | timestamp: number; | 195 | timestamp: number; |
| 196 | }, | 196 | }, |
| 197 | ) { | 197 | ) { |
| 198 | const anonymousOrganizationName = "Anonymous Organization"; | ||
| 199 | const anonymousWorkspaceName = "Anonymous Workspace"; | ||
| 198 | const now = params.timestamp; | 200 | const now = params.timestamp; |
| 199 | 201 | ||
| 200 | let account = await ctx.db | 202 | let account = await ctx.db |
| @@ -226,10 +228,10 @@ async function ensureAnonymousIdentity( · 25 unmodified lines | |||
| 226 | let organizationId: Doc<"organizations">["_id"]; | 228 | let organizationId: Doc<"organizations">["_id"]; |
| 227 | 229 | ||
| 228 | if (!workspace) { | 230 | if (!workspace) { |
| 229 | const organizationSlug = await ensureUniqueOrganizationSlug(ctx, "Guest Workspace"); | 231 | const organizationSlug = await ensureUniqueOrganizationSlug(ctx, anonymousOrganizationName); |
| 230 | organizationId = await ctx.db.insert("organizations", { | 232 | organizationId = await ctx.db.insert("organizations", { |
| 231 | slug: organizationSlug, | 233 | slug: organizationSlug, |
| 232 | name: "Guest Workspace", | 234 | name: anonymousOrganizationName, |
| 233 | status: "active", | 235 | status: "active", |
| 234 | createdByAccountId: account._id, | 236 | createdByAccountId: account._id, |
| 235 | createdAt: now, | 237 | createdAt: now, |
| @@ -238,8 +240,8 @@ async function ensureAnonymousIdentity( · 2 unmodified lines | |||
| 238 | 240 | ||
| 239 | const workspaceId = await ctx.db.insert("workspaces", { | 241 | const workspaceId = await ctx.db.insert("workspaces", { |
| 240 | organizationId, | 242 | organizationId, |
| 241 | slug: `guest-${crypto.randomUUID().slice(0, 8)}`, | 243 | slug: `anonymous-${crypto.randomUUID().slice(0, 8)}`, |
| 242 | name: "Guest Workspace", | 244 | name: anonymousWorkspaceName, |
| 243 | plan: "free", | 245 | plan: "free", |
| 244 | createdByAccountId: account._id, | 246 | createdByAccountId: account._id, |
| 245 | createdAt: now, | 247 | createdAt: now, |
| @@ -0,0 +1,95 @@ | |||
| 1 | import { Migrations } from "@convex-dev/migrations"; | ||
| 2 | import type { Id } from "./_generated/dataModel"; | ||
| 3 | import type { DataModel } from "./_generated/dataModel"; | ||
| 4 | import { components } from "./_generated/api"; | ||
| 5 | |||
| 6 | const migrations = new Migrations<DataModel>(components.migrations); | ||
| 7 | |||
| 8 | const LEGACY_GUEST_NAME = "Guest Workspace"; | ||
| 9 | const ANONYMOUS_ORGANIZATION_NAME = "Anonymous Organization"; | ||
| 10 | const ANONYMOUS_WORKSPACE_NAME = "Anonymous Workspace"; | ||
| 11 | |||
| 12 | async function isAnonymousCreator( | ||
| 13 | ctx: { db: { get: (id: Id<"accounts">) => Promise<{ provider: string } | null> } }, | ||
| 14 | accountId: Id<"accounts"> | undefined, | ||
| 15 | ): Promise<boolean> { | ||
| 16 | if (!accountId) { | ||
| 17 | return false; | ||
| 18 | } | ||
| 19 | |||
| 20 | const account = await ctx.db.get(accountId); | ||
| 21 | return account?.provider === "anonymous"; | ||
| 22 | } | ||
| 23 | |||
| 24 | export const renameLegacyAnonymousOrganizations = migrations.define({ | ||
| 25 | table: "organizations", | ||
| 26 | migrateOne: async (ctx, organization) => { | ||
| 27 | if (organization.name !== LEGACY_GUEST_NAME) { | ||
| 28 | return; | ||
| 29 | } | ||
| 30 | |||
| 31 | const anonymousCreator = await isAnonymousCreator(ctx, organization.createdByAccountId); | ||
| 32 | if (!anonymousCreator) { | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | return { | ||
| 37 | name: ANONYMOUS_ORGANIZATION_NAME, | ||
| 38 | updatedAt: Date.now(), | ||
| 39 | }; | ||
| 40 | }, | ||
| 41 | }); | ||
| 42 | |||
| 43 | export const renameLegacyAnonymousWorkspaces = migrations.define({ | ||
| 44 | table: "workspaces", | ||
| 45 | migrateOne: async (ctx, workspace) => { | ||
| 46 | if (workspace.name !== LEGACY_GUEST_NAME) { | ||
| 47 | return; | ||
| 48 | } | ||
| 49 | |||
| 50 | const anonymousCreator = await isAnonymousCreator(ctx, workspace.createdByAccountId); | ||
| 51 | if (!anonymousCreator) { | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 55 | return { | ||
| 56 | name: ANONYMOUS_WORKSPACE_NAME, | ||
| 57 | updatedAt: Date.now(), | ||
| 58 | }; | ||
| 59 | }, | ||
| 60 | }); | ||
| 61 | |||
| 62 | export const deleteAnonymousSessionsMissingAccountId = migrations.define({ | ||
| 63 | table: "anonymousSessions", | ||
| 64 | migrateOne: async (ctx, session) => { | ||
| 65 | if (session.accountId) { | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | |||
| 69 | await ctx.db.delete(session._id); | ||
| 70 | }, | ||
| 71 | }); | ||
| 72 | |||
| 73 | export const deleteSourceCredentialsMissingProvider = migrations.define({ | ||
| 74 | table: "sourceCredentials", | ||
| 75 | migrateOne: async (ctx, credential) => { | ||
| 76 | if (credential.provider) { | ||
| 77 | return; | ||
| 78 | } | ||
| 79 | |||
| 80 | await ctx.db.delete(credential._id); | ||
| 81 | }, | ||
| 82 | }); | ||
| 83 | |||
| 84 | export const deleteInvitesMissingProvider = migrations.define({ | ||
| 85 | table: "invites", | ||
| 86 | migrateOne: async (ctx, invite) => { | ||
| 87 | if (invite.provider) { | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | await ctx.db.delete(invite._id); | ||
| 92 | }, | ||
| 93 | }); | ||
| 94 | |||
| 95 | export const run = migrations.runner(); | ||
| @@ -130,7 +130,7 @@ export default defineSchema({ · 129 unmodified lines | |||
| 130 | email: v.string(), | 130 | email: v.string(), |
| 131 | role: orgRole, | 131 | role: orgRole, |
| 132 | status: inviteStatus, | 132 | status: inviteStatus, |
| 133 | provider: v.optional(inviteProvider), | 133 | provider: inviteProvider, |
| 134 | providerInviteId: v.optional(v.string()), | 134 | providerInviteId: v.optional(v.string()), |
| 135 | invitedByAccountId: v.id("accounts"), | 135 | invitedByAccountId: v.id("accounts"), |
| 136 | expiresAt: v.number(), | 136 | expiresAt: v.number(), |
| @@ -263,7 +263,7 @@ export default defineSchema({ · 126 unmodified lines | |||
| 263 | sourceKey: v.string(), | 263 | sourceKey: v.string(), |
| 264 | scope: credentialScope, | 264 | scope: credentialScope, |
| 265 | actorId: v.string(), | 265 | actorId: v.string(), |
| 266 | provider: v.optional(credentialProvider), | 266 | provider: credentialProvider, |
| 267 | secretJson: v.any(), | 267 | secretJson: v.any(), |
| 268 | createdAt: v.number(), | 268 | createdAt: v.number(), |
| 269 | updatedAt: v.number(), | 269 | updatedAt: v.number(), |
| @@ -333,7 +333,7 @@ export default defineSchema({ · 63 unmodified lines | |||
| 333 | workspaceId: v.id("workspaces"), | 333 | workspaceId: v.id("workspaces"), |
| 334 | actorId: v.string(), | 334 | actorId: v.string(), |
| 335 | clientId: v.string(), | 335 | clientId: v.string(), |
| 336 | accountId: v.optional(v.id("accounts")), | 336 | accountId: v.id("accounts"), |
| 337 | userId: v.optional(v.id("workspaceMembers")), | 337 | userId: v.optional(v.id("workspaceMembers")), |
| 338 | createdAt: v.number(), | 338 | createdAt: v.number(), |
| 339 | lastSeenAt: v.number(), | 339 | lastSeenAt: v.number(), |
| @@ -29,6 +29,7 @@ · 28 unmodified lines | |||
| 29 | }, | 29 | }, |
| 30 | "dependencies": { | 30 | "dependencies": { |
| 31 | "@apidevtools/swagger-parser": "^12.1.0", | 31 | "@apidevtools/swagger-parser": "^12.1.0", |
| 32 | "@convex-dev/migrations": "^0.3.1", | ||
| 32 | "@convex-dev/stripe": "^0.1.3", | 33 | "@convex-dev/stripe": "^0.1.3", |
| 33 | "@convex-dev/workos-authkit": "^0.1.6", | 34 | "@convex-dev/workos-authkit": "^0.1.6", |
| 34 | "@executor/contracts": "workspace:*", | 35 | "@executor/contracts": "workspace:*", |